~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Alexander Belchenko
  • Date: 2007-08-10 09:04:38 UTC
  • mto: This revision was merged to the branch mainline in revision 2694.
  • Revision ID: bialix@ukr.net-20070810090438-0835xdz0rl8825qv
fixes after Ian's review

Show diffs side-by-side

added added

removed removed

Lines of Context:
70
70
import errno
71
71
from fnmatch import fnmatch
72
72
import re
73
 
from cStringIO import StringIO
 
73
from StringIO import StringIO
74
74
 
75
75
import bzrlib
76
76
from bzrlib import (
77
 
    debug,
78
77
    errors,
79
 
    mail_client,
80
78
    osutils,
81
79
    symbol_versioning,
82
80
    trace,
83
 
    ui,
84
81
    urlutils,
85
82
    win32utils,
86
83
    )
87
 
from bzrlib.util.configobj import configobj
 
84
import bzrlib.util.configobj.configobj as configobj
88
85
""")
89
86
 
 
87
from bzrlib.trace import mutter, warning
 
88
 
90
89
 
91
90
CHECK_IF_POSSIBLE=0
92
91
CHECK_ALWAYS=1
144
143
        """Get the users pop up editor."""
145
144
        raise NotImplementedError
146
145
 
147
 
    def get_mail_client(self):
148
 
        """Get a mail client to use"""
149
 
        selected_client = self.get_user_option('mail_client')
150
 
        try:
151
 
            mail_client_class = {
152
 
                None: mail_client.DefaultMail,
153
 
                # Specific clients
154
 
                'evolution': mail_client.Evolution,
155
 
                'kmail': mail_client.KMail,
156
 
                'mutt': mail_client.Mutt,
157
 
                'thunderbird': mail_client.Thunderbird,
158
 
                # Generic options
159
 
                'default': mail_client.DefaultMail,
160
 
                'editor': mail_client.Editor,
161
 
                'mapi': mail_client.MAPIClient,
162
 
                'xdg-email': mail_client.XDGEmail,
163
 
            }[selected_client]
164
 
        except KeyError:
165
 
            raise errors.UnknownMailClient(selected_client)
166
 
        return mail_client_class(self)
167
 
 
168
146
    def _get_signature_checking(self):
169
147
        """Template method to override signature checking policy."""
170
148
 
236
214
        v = os.environ.get('BZR_EMAIL')
237
215
        if v:
238
216
            return v.decode(bzrlib.user_encoding)
239
 
 
 
217
        v = os.environ.get('BZREMAIL')
 
218
        if v:
 
219
            warning('BZREMAIL is deprecated in favor of BZR_EMAIL. Please update your configuration.')
 
220
            return v.decode(bzrlib.user_encoding)
 
221
    
240
222
        v = self._get_user_id()
241
223
        if v:
242
224
            return v
243
 
 
 
225
        
244
226
        v = os.environ.get('EMAIL')
245
227
        if v:
246
228
            return v.decode(bzrlib.user_encoding)
271
253
        if policy is None:
272
254
            policy = self._get_signature_checking()
273
255
            if policy is not None:
274
 
                trace.warning("Please use create_signatures,"
275
 
                              " not check_signatures to set signing policy.")
 
256
                warning("Please use create_signatures, not check_signatures "
 
257
                        "to set signing policy.")
276
258
            if policy == CHECK_ALWAYS:
277
259
                return True
278
260
        elif policy == SIGN_ALWAYS:
291
273
    def _get_nickname(self):
292
274
        return None
293
275
 
294
 
    def get_bzr_remote_path(self):
295
 
        try:
296
 
            return os.environ['BZR_REMOTE_PATH']
297
 
        except KeyError:
298
 
            path = self.get_user_option("bzr_remote_path")
299
 
            if path is None:
300
 
                path = 'bzr'
301
 
            return path
302
 
 
303
276
 
304
277
class IniBasedConfig(Config):
305
278
    """A configuration policy that draws from ini files."""
456
429
 
457
430
    def __init__(self, location):
458
431
        name_generator = locations_config_filename
459
 
        if (not os.path.exists(name_generator()) and
 
432
        if (not os.path.exists(name_generator()) and 
460
433
                os.path.exists(branches_config_filename())):
461
434
            if sys.platform == 'win32':
462
 
                trace.warning('Please rename %s to %s'
463
 
                              % (branches_config_filename(),
464
 
                                 locations_config_filename()))
 
435
                warning('Please rename %s to %s' 
 
436
                         % (branches_config_filename(),
 
437
                            locations_config_filename()))
465
438
            else:
466
 
                trace.warning('Please rename ~/.bazaar/branches.conf'
467
 
                              ' to ~/.bazaar/locations.conf')
 
439
                warning('Please rename ~/.bazaar/branches.conf'
 
440
                        ' to ~/.bazaar/locations.conf')
468
441
            name_generator = branches_config_filename
469
442
        super(LocationConfig, self).__init__(name_generator)
470
443
        # local file locations are looked up by local path, rather than
739
712
        if sys.platform == 'win32':
740
713
            parent_dir = os.path.dirname(path)
741
714
            if not os.path.isdir(parent_dir):
742
 
                trace.mutter('creating config parent directory: %r', parent_dir)
 
715
                mutter('creating config parent directory: %r', parent_dir)
743
716
            os.mkdir(parent_dir)
744
 
        trace.mutter('creating config directory: %r', path)
 
717
        mutter('creating config directory: %r', path)
745
718
        os.mkdir(path)
746
719
 
747
720
 
759
732
        if base is None:
760
733
            base = os.environ.get('HOME', None)
761
734
        if base is None:
762
 
            raise errors.BzrError('You must have one of BZR_HOME, APPDATA,'
763
 
                                  ' or HOME set')
 
735
            raise errors.BzrError('You must have one of BZR_HOME, APPDATA, or HOME set')
764
736
        return osutils.pathjoin(base, 'bazaar', '2.0')
765
737
    else:
766
738
        # cygwin, linux, and darwin all have a $HOME directory
784
756
    return osutils.pathjoin(config_dir(), 'locations.conf')
785
757
 
786
758
 
787
 
def authentication_config_filename():
788
 
    """Return per-user authentication ini file filename."""
789
 
    return osutils.pathjoin(config_dir(), 'authentication.conf')
790
 
 
791
 
 
792
759
def user_ignore_config_filename():
793
760
    """Return the user default ignore filename"""
794
761
    return osutils.pathjoin(config_dir(), 'ignore')
862
829
    return realname, (username + '@' + socket.gethostname())
863
830
 
864
831
 
865
 
def parse_username(username):
866
 
    """Parse e-mail username and return a (name, address) tuple."""
867
 
    match = re.match(r'(.*?)\s*<?([\w+.-]+@[\w+.-]+)>?', username)
868
 
    if match is None:
869
 
        return (username, '')
870
 
    else:
871
 
        return (match.group(1), match.group(2))
872
 
 
873
 
 
874
832
def extract_email_address(e):
875
833
    """Return just the address part of an email string.
876
 
 
 
834
    
877
835
    That is just the user@domain part, nothing else. 
878
836
    This part is required to contain only ascii characters.
879
837
    If it can't be extracted, raises an error.
880
 
 
 
838
    
881
839
    >>> extract_email_address('Jane Tester <jane@test.com>')
882
840
    "jane@test.com"
883
841
    """
884
 
    name, email = parse_username(e)
885
 
    if not email:
 
842
    m = re.search(r'[\w+.-]+@[\w+.-]+', e)
 
843
    if not m:
886
844
        raise errors.NoEmailInUsername(e)
887
 
    return email
 
845
    return m.group(0)
888
846
 
889
847
 
890
848
class TreeConfig(IniBasedConfig):
891
849
    """Branch configuration data associated with its contents, not location"""
892
 
 
893
850
    def __init__(self, branch):
894
851
        self.branch = branch
895
852
 
940
897
            self.branch.control_files.put('branch.conf', out_file)
941
898
        finally:
942
899
            self.branch.unlock()
943
 
 
944
 
 
945
 
class AuthenticationConfig(object):
946
 
    """The authentication configuration file based on a ini file.
947
 
 
948
 
    Implements the authentication.conf file described in
949
 
    doc/developers/authentication-ring.txt.
950
 
    """
951
 
 
952
 
    def __init__(self, _file=None):
953
 
        self._config = None # The ConfigObj
954
 
        if _file is None:
955
 
            self._filename = authentication_config_filename()
956
 
            self._input = self._filename = authentication_config_filename()
957
 
        else:
958
 
            # Tests can provide a string as _file
959
 
            self._filename = None
960
 
            self._input = _file
961
 
 
962
 
    def _get_config(self):
963
 
        if self._config is not None:
964
 
            return self._config
965
 
        try:
966
 
            # FIXME: Should we validate something here ? Includes: empty
967
 
            # sections are useless, at least one of
968
 
            # user/password/password_encoding should be defined, etc.
969
 
 
970
 
            # Note: the encoding below declares that the file itself is utf-8
971
 
            # encoded, but the values in the ConfigObj are always Unicode.
972
 
            self._config = ConfigObj(self._input, encoding='utf-8')
973
 
        except configobj.ConfigObjError, e:
974
 
            raise errors.ParseConfigError(e.errors, e.config.filename)
975
 
        return self._config
976
 
 
977
 
    def _save(self):
978
 
        """Save the config file, only tests should use it for now."""
979
 
        conf_dir = os.path.dirname(self._filename)
980
 
        ensure_config_dir_exists(conf_dir)
981
 
        self._get_config().write(file(self._filename, 'wb'))
982
 
 
983
 
    def _set_option(self, section_name, option_name, value):
984
 
        """Set an authentication configuration option"""
985
 
        conf = self._get_config()
986
 
        section = conf.get(section_name)
987
 
        if section is None:
988
 
            conf[section] = {}
989
 
            section = conf[section]
990
 
        section[option_name] = value
991
 
        self._save()
992
 
 
993
 
    def get_credentials(self, scheme, host, port=None, user=None, path=None):
994
 
        """Returns the matching credentials from authentication.conf file.
995
 
 
996
 
        :param scheme: protocol
997
 
 
998
 
        :param host: the server address
999
 
 
1000
 
        :param port: the associated port (optional)
1001
 
 
1002
 
        :param user: login (optional)
1003
 
 
1004
 
        :param path: the absolute path on the server (optional)
1005
 
 
1006
 
        :return: A dict containing the matching credentials or None.
1007
 
           This includes:
1008
 
           - name: the section name of the credentials in the
1009
 
             authentication.conf file,
1010
 
           - user: can't de different from the provided user if any,
1011
 
           - password: the decoded password, could be None if the credential
1012
 
             defines only the user
1013
 
           - verify_certificates: https specific, True if the server
1014
 
             certificate should be verified, False otherwise.
1015
 
        """
1016
 
        credentials = None
1017
 
        for auth_def_name, auth_def in self._get_config().items():
1018
 
            a_scheme, a_host, a_user, a_path = map(
1019
 
                auth_def.get, ['scheme', 'host', 'user', 'path'])
1020
 
 
1021
 
            try:
1022
 
                a_port = auth_def.as_int('port')
1023
 
            except KeyError:
1024
 
                a_port = None
1025
 
            except ValueError:
1026
 
                raise ValueError("'port' not numeric in %s" % auth_def_name)
1027
 
            try:
1028
 
                a_verify_certificates = auth_def.as_bool('verify_certificates')
1029
 
            except KeyError:
1030
 
                a_verify_certificates = True
1031
 
            except ValueError:
1032
 
                raise ValueError(
1033
 
                    "'verify_certificates' not boolean in %s" % auth_def_name)
1034
 
 
1035
 
            # Attempt matching
1036
 
            if a_scheme is not None and scheme != a_scheme:
1037
 
                continue
1038
 
            if a_host is not None:
1039
 
                if not (host == a_host
1040
 
                        or (a_host.startswith('.') and host.endswith(a_host))):
1041
 
                    continue
1042
 
            if a_port is not None and port != a_port:
1043
 
                continue
1044
 
            if (a_path is not None and path is not None
1045
 
                and not path.startswith(a_path)):
1046
 
                continue
1047
 
            if (a_user is not None and user is not None
1048
 
                and a_user != user):
1049
 
                # Never contradict the caller about the user to be used
1050
 
                continue
1051
 
            if a_user is None:
1052
 
                # Can't find a user
1053
 
                continue
1054
 
            credentials = dict(name=auth_def_name,
1055
 
                               user=a_user, password=auth_def['password'],
1056
 
                               verify_certificates=a_verify_certificates)
1057
 
            self.decode_password(credentials,
1058
 
                                 auth_def.get('password_encoding', None))
1059
 
            if 'auth' in debug.debug_flags:
1060
 
                trace.mutter("Using authentication section: %r", auth_def_name)
1061
 
            break
1062
 
 
1063
 
        return credentials
1064
 
 
1065
 
    def get_user(self, scheme, host, port=None,
1066
 
                 realm=None, path=None, prompt=None):
1067
 
        """Get a user from authentication file.
1068
 
 
1069
 
        :param scheme: protocol
1070
 
 
1071
 
        :param host: the server address
1072
 
 
1073
 
        :param port: the associated port (optional)
1074
 
 
1075
 
        :param realm: the realm sent by the server (optional)
1076
 
 
1077
 
        :param path: the absolute path on the server (optional)
1078
 
 
1079
 
        :return: The found user.
1080
 
        """
1081
 
        credentials = self.get_credentials(scheme, host, port, user=None,
1082
 
                                           path=path)
1083
 
        if credentials is not None:
1084
 
            user = credentials['user']
1085
 
        else:
1086
 
            user = None
1087
 
        return user
1088
 
 
1089
 
    def get_password(self, scheme, host, user, port=None,
1090
 
                     realm=None, path=None, prompt=None):
1091
 
        """Get a password from authentication file or prompt the user for one.
1092
 
 
1093
 
        :param scheme: protocol
1094
 
 
1095
 
        :param host: the server address
1096
 
 
1097
 
        :param port: the associated port (optional)
1098
 
 
1099
 
        :param user: login
1100
 
 
1101
 
        :param realm: the realm sent by the server (optional)
1102
 
 
1103
 
        :param path: the absolute path on the server (optional)
1104
 
 
1105
 
        :return: The found password or the one entered by the user.
1106
 
        """
1107
 
        credentials = self.get_credentials(scheme, host, port, user, path)
1108
 
        if credentials is not None:
1109
 
            password = credentials['password']
1110
 
        else:
1111
 
            password = None
1112
 
        # Prompt user only if we could't find a password
1113
 
        if password is None:
1114
 
            if prompt is None:
1115
 
                # Create a default prompt suitable for most of the cases
1116
 
                prompt = '%s' % scheme.upper() + ' %(user)s@%(host)s password'
1117
 
            # Special handling for optional fields in the prompt
1118
 
            if port is not None:
1119
 
                prompt_host = '%s:%d' % (host, port)
1120
 
            else:
1121
 
                prompt_host = host
1122
 
            password = ui.ui_factory.get_password(prompt,
1123
 
                                                  host=prompt_host, user=user)
1124
 
        return password
1125
 
 
1126
 
    def decode_password(self, credentials, encoding):
1127
 
        return credentials