~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/config.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-11-04 18:51:39 UTC
  • mfrom: (2961.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20071104185139-kaio3sneodg2kp71
Authentication ring implementation (read-only)

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 StringIO import StringIO
 
73
from cStringIO import StringIO
74
74
 
75
75
import bzrlib
76
76
from bzrlib import (
 
77
    debug,
77
78
    errors,
78
79
    mail_client,
79
80
    osutils,
80
81
    symbol_versioning,
81
82
    trace,
 
83
    ui,
82
84
    urlutils,
83
85
    win32utils,
84
86
    )
85
87
import bzrlib.util.configobj.configobj as configobj
86
88
""")
87
89
 
88
 
from bzrlib.trace import mutter, warning
89
 
 
90
90
 
91
91
CHECK_IF_POSSIBLE=0
92
92
CHECK_ALWAYS=1
236
236
        v = os.environ.get('BZR_EMAIL')
237
237
        if v:
238
238
            return v.decode(bzrlib.user_encoding)
239
 
        v = os.environ.get('BZREMAIL')
240
 
        if v:
241
 
            warning('BZREMAIL is deprecated in favor of BZR_EMAIL. Please update your configuration.')
242
 
            return v.decode(bzrlib.user_encoding)
243
 
    
 
239
 
244
240
        v = self._get_user_id()
245
241
        if v:
246
242
            return v
247
 
        
 
243
 
248
244
        v = os.environ.get('EMAIL')
249
245
        if v:
250
246
            return v.decode(bzrlib.user_encoding)
275
271
        if policy is None:
276
272
            policy = self._get_signature_checking()
277
273
            if policy is not None:
278
 
                warning("Please use create_signatures, not check_signatures "
279
 
                        "to set signing policy.")
 
274
                trace.warning("Please use create_signatures,"
 
275
                              " not check_signatures to set signing policy.")
280
276
            if policy == CHECK_ALWAYS:
281
277
                return True
282
278
        elif policy == SIGN_ALWAYS:
460
456
 
461
457
    def __init__(self, location):
462
458
        name_generator = locations_config_filename
463
 
        if (not os.path.exists(name_generator()) and 
 
459
        if (not os.path.exists(name_generator()) and
464
460
                os.path.exists(branches_config_filename())):
465
461
            if sys.platform == 'win32':
466
 
                warning('Please rename %s to %s' 
467
 
                         % (branches_config_filename(),
468
 
                            locations_config_filename()))
 
462
                trace.warning('Please rename %s to %s'
 
463
                              % (branches_config_filename(),
 
464
                                 locations_config_filename()))
469
465
            else:
470
 
                warning('Please rename ~/.bazaar/branches.conf'
471
 
                        ' to ~/.bazaar/locations.conf')
 
466
                trace.warning('Please rename ~/.bazaar/branches.conf'
 
467
                              ' to ~/.bazaar/locations.conf')
472
468
            name_generator = branches_config_filename
473
469
        super(LocationConfig, self).__init__(name_generator)
474
470
        # local file locations are looked up by local path, rather than
743
739
        if sys.platform == 'win32':
744
740
            parent_dir = os.path.dirname(path)
745
741
            if not os.path.isdir(parent_dir):
746
 
                mutter('creating config parent directory: %r', parent_dir)
 
742
                trace.mutter('creating config parent directory: %r', parent_dir)
747
743
            os.mkdir(parent_dir)
748
 
        mutter('creating config directory: %r', path)
 
744
        trace.mutter('creating config directory: %r', path)
749
745
        os.mkdir(path)
750
746
 
751
747
 
787
783
    return osutils.pathjoin(config_dir(), 'locations.conf')
788
784
 
789
785
 
 
786
def authentication_config_filename():
 
787
    """Return per-user authentication ini file filename."""
 
788
    return osutils.pathjoin(config_dir(), 'authentication.conf')
 
789
 
 
790
 
790
791
def user_ignore_config_filename():
791
792
    """Return the user default ignore filename"""
792
793
    return osutils.pathjoin(config_dir(), 'ignore')
878
879
 
879
880
class TreeConfig(IniBasedConfig):
880
881
    """Branch configuration data associated with its contents, not location"""
 
882
 
881
883
    def __init__(self, branch):
882
884
        self.branch = branch
883
885
 
928
930
            self.branch.control_files.put('branch.conf', out_file)
929
931
        finally:
930
932
            self.branch.unlock()
 
933
 
 
934
 
 
935
class AuthenticationConfig(object):
 
936
    """The authentication configuration file based on a ini file.
 
937
 
 
938
    Implements the authentication.conf file described in
 
939
    doc/developers/authentication-ring.txt.
 
940
    """
 
941
 
 
942
    def __init__(self, _file=None):
 
943
        self._config = None # The ConfigObj
 
944
        if _file is None:
 
945
            self._filename = authentication_config_filename()
 
946
            self._input = self._filename = authentication_config_filename()
 
947
        else:
 
948
            # Tests can provide a string as _file
 
949
            self._filename = None
 
950
            self._input = _file
 
951
 
 
952
    def _get_config(self):
 
953
        if self._config is not None:
 
954
            return self._config
 
955
        try:
 
956
            # FIXME: Should we validate something here ? Includes: empty
 
957
            # sections are useless, at least one of
 
958
            # user/password/password_encoding should be defined, etc.
 
959
 
 
960
            # Note: the encoding below declares that the file itself is utf-8
 
961
            # encoded, but the values in the ConfigObj are always Unicode.
 
962
            self._config = ConfigObj(self._input, encoding='utf-8')
 
963
        except configobj.ConfigObjError, e:
 
964
            raise errors.ParseConfigError(e.errors, e.config.filename)
 
965
        return self._config
 
966
 
 
967
    def _save(self):
 
968
        """Save the config file, only tests should use it for now."""
 
969
        conf_dir = os.path.dirname(self._filename)
 
970
        ensure_config_dir_exists(conf_dir)
 
971
        self._get_config().write(file(self._filename, 'wb'))
 
972
 
 
973
    def _set_option(self, section_name, option_name, value):
 
974
        """Set an authentication configuration option"""
 
975
        conf = self._get_config()
 
976
        section = conf.get(section_name)
 
977
        if section is None:
 
978
            conf[section] = {}
 
979
            section = conf[section]
 
980
        section[option_name] = value
 
981
        self._save()
 
982
 
 
983
    def get_credentials(self, scheme, host, port=None, user=None, path=None):
 
984
        """Returns the matching credentials from authentication.conf file.
 
985
 
 
986
        :param scheme: protocol
 
987
 
 
988
        :param host: the server address
 
989
 
 
990
        :param port: the associated port (optional)
 
991
 
 
992
        :param user: login (optional)
 
993
 
 
994
        :param path: the absolute path on the server (optional)
 
995
 
 
996
        :return: A dict containing the matching credentials or None.
 
997
           This includes:
 
998
           - name: the section name of the credentials in the
 
999
             authentication.conf file,
 
1000
           - user: can't de different from the provided user if any,
 
1001
           - password: the decoded password, could be None if the credential
 
1002
             defines only the user
 
1003
           - verify_certificates: https specific, True if the server
 
1004
             certificate should be verified, False otherwise.
 
1005
        """
 
1006
        credentials = None
 
1007
        for auth_def_name, auth_def in self._get_config().items():
 
1008
            a_scheme, a_host, a_user, a_path = map(
 
1009
                auth_def.get, ['scheme', 'host', 'user', 'path'])
 
1010
 
 
1011
            try:
 
1012
                a_port = auth_def.as_int('port')
 
1013
            except KeyError:
 
1014
                a_port = None
 
1015
            except ValueError:
 
1016
                raise ValueError("'port' not numeric in %s" % auth_def_name)
 
1017
            try:
 
1018
                a_verify_certificates = auth_def.as_bool('verify_certificates')
 
1019
            except KeyError:
 
1020
                a_verify_certificates = True
 
1021
            except ValueError:
 
1022
                raise ValueError(
 
1023
                    "'verify_certificates' not boolean in %s" % auth_def_name)
 
1024
 
 
1025
            # Attempt matching
 
1026
            if a_scheme is not None and scheme != a_scheme:
 
1027
                continue
 
1028
            if a_host is not None:
 
1029
                if not (host == a_host
 
1030
                        or (a_host.startswith('.') and host.endswith(a_host))):
 
1031
                    continue
 
1032
            if a_port is not None and port != a_port:
 
1033
                continue
 
1034
            if (a_path is not None and path is not None
 
1035
                and not path.startswith(a_path)):
 
1036
                continue
 
1037
            if (a_user is not None and user is not None
 
1038
                and a_user != user):
 
1039
                # Never contradict the caller about the user to be used
 
1040
                continue
 
1041
            if a_user is None:
 
1042
                # Can't find a user
 
1043
                continue
 
1044
            credentials = dict(name=auth_def_name,
 
1045
                               user=a_user, password=auth_def['password'],
 
1046
                               verify_certificates=a_verify_certificates)
 
1047
            self.decode_password(credentials,
 
1048
                                 auth_def.get('password_encoding', None))
 
1049
            if 'auth' in debug.debug_flags:
 
1050
                trace.mutter("Using authentication section: %r", auth_def_name)
 
1051
            break
 
1052
 
 
1053
        return credentials
 
1054
 
 
1055
    def get_user(self, scheme, host, port=None,
 
1056
                 realm=None, path=None, prompt=None):
 
1057
        """Get a user from authentication file.
 
1058
 
 
1059
        :param scheme: protocol
 
1060
 
 
1061
        :param host: the server address
 
1062
 
 
1063
        :param port: the associated port (optional)
 
1064
 
 
1065
        :param realm: the realm sent by the server (optional)
 
1066
 
 
1067
        :param path: the absolute path on the server (optional)
 
1068
 
 
1069
        :return: The found user.
 
1070
        """
 
1071
        credentials = self.get_credentials(scheme, host, port, user=None,
 
1072
                                           path=path)
 
1073
        if credentials is not None:
 
1074
            user = credentials['user']
 
1075
        else:
 
1076
            user = None
 
1077
        return user
 
1078
 
 
1079
    def get_password(self, scheme, host, user, port=None,
 
1080
                     realm=None, path=None, prompt=None):
 
1081
        """Get a password from authentication file or prompt the user for one.
 
1082
 
 
1083
        :param scheme: protocol
 
1084
 
 
1085
        :param host: the server address
 
1086
 
 
1087
        :param port: the associated port (optional)
 
1088
 
 
1089
        :param user: login
 
1090
 
 
1091
        :param realm: the realm sent by the server (optional)
 
1092
 
 
1093
        :param path: the absolute path on the server (optional)
 
1094
 
 
1095
        :return: The found password or the one entered by the user.
 
1096
        """
 
1097
        credentials = self.get_credentials(scheme, host, port, user, path)
 
1098
        if credentials is not None:
 
1099
            password = credentials['password']
 
1100
        else:
 
1101
            password = None
 
1102
        # Prompt user only if we could't find a password
 
1103
        if password is None:
 
1104
            if prompt is None:
 
1105
                # Create a default prompt suitable for most of the cases
 
1106
                prompt = '%s' % scheme.upper() + ' %(user)s@%(host)s password'
 
1107
            # Special handling for optional fields in the prompt
 
1108
            if port is not None:
 
1109
                prompt_host = '%s:%d' % (host, port)
 
1110
            else:
 
1111
                prompt_host = host
 
1112
            password = ui.ui_factory.get_password(prompt,
 
1113
                                                  host=prompt_host, user=user)
 
1114
        return password
 
1115
 
 
1116
    def decode_password(self, credentials, encoding):
 
1117
        return credentials