739
936
"""Set a per-branch configuration option"""
740
937
self.branch.lock_write()
742
cfg_obj = self._get_config()
747
obj = cfg_obj[section]
749
cfg_obj[section] = {}
750
obj = cfg_obj[section]
752
out_file = StringIO()
753
cfg_obj.write(out_file)
755
self.branch.control_files.put('branch.conf', out_file)
939
self._config.set_option(value, name, section)
757
941
self.branch.unlock()
944
class AuthenticationConfig(object):
945
"""The authentication configuration file based on a ini file.
947
Implements the authentication.conf file described in
948
doc/developers/authentication-ring.txt.
951
def __init__(self, _file=None):
952
self._config = None # The ConfigObj
954
self._filename = authentication_config_filename()
955
self._input = self._filename = authentication_config_filename()
957
# Tests can provide a string as _file
958
self._filename = None
961
def _get_config(self):
962
if self._config is not None:
965
# FIXME: Should we validate something here ? Includes: empty
966
# sections are useless, at least one of
967
# user/password/password_encoding should be defined, etc.
969
# Note: the encoding below declares that the file itself is utf-8
970
# encoded, but the values in the ConfigObj are always Unicode.
971
self._config = ConfigObj(self._input, encoding='utf-8')
972
except configobj.ConfigObjError, e:
973
raise errors.ParseConfigError(e.errors, e.config.filename)
977
"""Save the config file, only tests should use it for now."""
978
conf_dir = os.path.dirname(self._filename)
979
ensure_config_dir_exists(conf_dir)
980
self._get_config().write(file(self._filename, 'wb'))
982
def _set_option(self, section_name, option_name, value):
983
"""Set an authentication configuration option"""
984
conf = self._get_config()
985
section = conf.get(section_name)
988
section = conf[section]
989
section[option_name] = value
992
def get_credentials(self, scheme, host, port=None, user=None, path=None,
994
"""Returns the matching credentials from authentication.conf file.
996
:param scheme: protocol
998
:param host: the server address
1000
:param port: the associated port (optional)
1002
:param user: login (optional)
1004
:param path: the absolute path on the server (optional)
1006
:param realm: the http authentication realm (optional)
1008
:return: A dict containing the matching credentials or None.
1010
- name: the section name of the credentials in the
1011
authentication.conf file,
1012
- user: can't be different from the provided user if any,
1013
- scheme: the server protocol,
1014
- host: the server address,
1015
- port: the server port (can be None),
1016
- path: the absolute server path (can be None),
1017
- realm: the http specific authentication realm (can be None),
1018
- password: the decoded password, could be None if the credential
1019
defines only the user
1020
- verify_certificates: https specific, True if the server
1021
certificate should be verified, False otherwise.
1024
for auth_def_name, auth_def in self._get_config().items():
1025
if type(auth_def) is not configobj.Section:
1026
raise ValueError("%s defined outside a section" % auth_def_name)
1028
a_scheme, a_host, a_user, a_path = map(
1029
auth_def.get, ['scheme', 'host', 'user', 'path'])
1032
a_port = auth_def.as_int('port')
1036
raise ValueError("'port' not numeric in %s" % auth_def_name)
1038
a_verify_certificates = auth_def.as_bool('verify_certificates')
1040
a_verify_certificates = True
1043
"'verify_certificates' not boolean in %s" % auth_def_name)
1046
if a_scheme is not None and scheme != a_scheme:
1048
if a_host is not None:
1049
if not (host == a_host
1050
or (a_host.startswith('.') and host.endswith(a_host))):
1052
if a_port is not None and port != a_port:
1054
if (a_path is not None and path is not None
1055
and not path.startswith(a_path)):
1057
if (a_user is not None and user is not None
1058
and a_user != user):
1059
# Never contradict the caller about the user to be used
1064
# Prepare a credentials dictionary with additional keys
1065
# for the credential providers
1066
credentials = dict(name=auth_def_name,
1073
password=auth_def.get('password', None),
1074
verify_certificates=a_verify_certificates)
1075
# Decode the password in the credentials (or get one)
1076
self.decode_password(credentials,
1077
auth_def.get('password_encoding', None))
1078
if 'auth' in debug.debug_flags:
1079
trace.mutter("Using authentication section: %r", auth_def_name)
1082
if credentials is None:
1083
# No credentials were found in authentication.conf, try the fallback
1084
# credentials stores.
1085
credentials = credential_store_registry.get_fallback_credentials(
1086
scheme, host, port, user, path, realm)
1090
def set_credentials(self, name, host, user, scheme=None, password=None,
1091
port=None, path=None, verify_certificates=None,
1093
"""Set authentication credentials for a host.
1095
Any existing credentials with matching scheme, host, port and path
1096
will be deleted, regardless of name.
1098
:param name: An arbitrary name to describe this set of credentials.
1099
:param host: Name of the host that accepts these credentials.
1100
:param user: The username portion of these credentials.
1101
:param scheme: The URL scheme (e.g. ssh, http) the credentials apply
1103
:param password: Password portion of these credentials.
1104
:param port: The IP port on the host that these credentials apply to.
1105
:param path: A filesystem path on the host that these credentials
1107
:param verify_certificates: On https, verify server certificates if
1109
:param realm: The http authentication realm (optional).
1111
values = {'host': host, 'user': user}
1112
if password is not None:
1113
values['password'] = password
1114
if scheme is not None:
1115
values['scheme'] = scheme
1116
if port is not None:
1117
values['port'] = '%d' % port
1118
if path is not None:
1119
values['path'] = path
1120
if verify_certificates is not None:
1121
values['verify_certificates'] = str(verify_certificates)
1122
if realm is not None:
1123
values['realm'] = realm
1124
config = self._get_config()
1126
for section, existing_values in config.items():
1127
for key in ('scheme', 'host', 'port', 'path', 'realm'):
1128
if existing_values.get(key) != values.get(key):
1132
config.update({name: values})
1135
def get_user(self, scheme, host, port=None, realm=None, path=None,
1136
prompt=None, ask=False, default=None):
1137
"""Get a user from authentication file.
1139
:param scheme: protocol
1141
:param host: the server address
1143
:param port: the associated port (optional)
1145
:param realm: the realm sent by the server (optional)
1147
:param path: the absolute path on the server (optional)
1149
:param ask: Ask the user if there is no explicitly configured username
1152
:return: The found user.
1154
credentials = self.get_credentials(scheme, host, port, user=None,
1155
path=path, realm=realm)
1156
if credentials is not None:
1157
user = credentials['user']
1163
# Create a default prompt suitable for most cases
1164
prompt = scheme.upper() + ' %(host)s username'
1165
# Special handling for optional fields in the prompt
1166
if port is not None:
1167
prompt_host = '%s:%d' % (host, port)
1170
user = ui.ui_factory.get_username(prompt, host=prompt_host)
1174
default = getpass.getuser()
1178
def get_password(self, scheme, host, user, port=None,
1179
realm=None, path=None, prompt=None):
1180
"""Get a password from authentication file or prompt the user for one.
1182
:param scheme: protocol
1184
:param host: the server address
1186
:param port: the associated port (optional)
1190
:param realm: the realm sent by the server (optional)
1192
:param path: the absolute path on the server (optional)
1194
:return: The found password or the one entered by the user.
1196
credentials = self.get_credentials(scheme, host, port, user, path,
1198
if credentials is not None:
1199
password = credentials['password']
1200
if password is not None and scheme is 'ssh':
1201
trace.warning('password ignored in section [%s],'
1202
' use an ssh agent instead'
1203
% credentials['name'])
1207
# Prompt user only if we could't find a password
1208
if password is None:
1210
# Create a default prompt suitable for most cases
1211
prompt = '%s' % scheme.upper() + ' %(user)s@%(host)s password'
1212
# Special handling for optional fields in the prompt
1213
if port is not None:
1214
prompt_host = '%s:%d' % (host, port)
1217
password = ui.ui_factory.get_password(prompt,
1218
host=prompt_host, user=user)
1221
def decode_password(self, credentials, encoding):
1223
cs = credential_store_registry.get_credential_store(encoding)
1225
raise ValueError('%r is not a known password_encoding' % encoding)
1226
credentials['password'] = cs.decode_password(credentials)
1230
class CredentialStoreRegistry(registry.Registry):
1231
"""A class that registers credential stores.
1233
A credential store provides access to credentials via the password_encoding
1234
field in authentication.conf sections.
1236
Except for stores provided by bzr itself, most stores are expected to be
1237
provided by plugins that will therefore use
1238
register_lazy(password_encoding, module_name, member_name, help=help,
1239
fallback=fallback) to install themselves.
1241
A fallback credential store is one that is queried if no credentials can be
1242
found via authentication.conf.
1245
def get_credential_store(self, encoding=None):
1246
cs = self.get(encoding)
1251
def is_fallback(self, name):
1252
"""Check if the named credentials store should be used as fallback."""
1253
return self.get_info(name)
1255
def get_fallback_credentials(self, scheme, host, port=None, user=None,
1256
path=None, realm=None):
1257
"""Request credentials from all fallback credentials stores.
1259
The first credentials store that can provide credentials wins.
1262
for name in self.keys():
1263
if not self.is_fallback(name):
1265
cs = self.get_credential_store(name)
1266
credentials = cs.get_credentials(scheme, host, port, user,
1268
if credentials is not None:
1269
# We found some credentials
1273
def register(self, key, obj, help=None, override_existing=False,
1275
"""Register a new object to a name.
1277
:param key: This is the key to use to request the object later.
1278
:param obj: The object to register.
1279
:param help: Help text for this entry. This may be a string or
1280
a callable. If it is a callable, it should take two
1281
parameters (registry, key): this registry and the key that
1282
the help was registered under.
1283
:param override_existing: Raise KeyErorr if False and something has
1284
already been registered for that key. If True, ignore if there
1285
is an existing key (always register the new value).
1286
:param fallback: Whether this credential store should be
1289
return super(CredentialStoreRegistry,
1290
self).register(key, obj, help, info=fallback,
1291
override_existing=override_existing)
1293
def register_lazy(self, key, module_name, member_name,
1294
help=None, override_existing=False,
1296
"""Register a new credential store to be loaded on request.
1298
:param module_name: The python path to the module. Such as 'os.path'.
1299
:param member_name: The member of the module to return. If empty or
1300
None, get() will return the module itself.
1301
:param help: Help text for this entry. This may be a string or
1303
:param override_existing: If True, replace the existing object
1304
with the new one. If False, if there is already something
1305
registered with the same key, raise a KeyError
1306
:param fallback: Whether this credential store should be
1309
return super(CredentialStoreRegistry, self).register_lazy(
1310
key, module_name, member_name, help,
1311
info=fallback, override_existing=override_existing)
1314
credential_store_registry = CredentialStoreRegistry()
1317
class CredentialStore(object):
1318
"""An abstract class to implement storage for credentials"""
1320
def decode_password(self, credentials):
1321
"""Returns a clear text password for the provided credentials."""
1322
raise NotImplementedError(self.decode_password)
1324
def get_credentials(self, scheme, host, port=None, user=None, path=None,
1326
"""Return the matching credentials from this credential store.
1328
This method is only called on fallback credential stores.
1330
raise NotImplementedError(self.get_credentials)
1334
class PlainTextCredentialStore(CredentialStore):
1335
"""Plain text credential store for the authentication.conf file."""
1337
def decode_password(self, credentials):
1338
"""See CredentialStore.decode_password."""
1339
return credentials['password']
1342
credential_store_registry.register('plain', PlainTextCredentialStore,
1343
help=PlainTextCredentialStore.__doc__)
1344
credential_store_registry.default_key = 'plain'
1347
class BzrDirConfig(object):
1349
def __init__(self, bzrdir):
1350
self._bzrdir = bzrdir
1351
self._config = bzrdir._get_config()
1353
def set_default_stack_on(self, value):
1354
"""Set the default stacking location.
1356
It may be set to a location, or None.
1358
This policy affects all branches contained by this bzrdir, except for
1359
those under repositories.
1361
if self._config is None:
1362
raise errors.BzrError("Cannot set configuration in %s" % self._bzrdir)
1364
self._config.set_option('', 'default_stack_on')
1366
self._config.set_option(value, 'default_stack_on')
1368
def get_default_stack_on(self):
1369
"""Return the default stacking location.
1371
This will either be a location, or None.
1373
This policy affects all branches contained by this bzrdir, except for
1374
those under repositories.
1376
if self._config is None:
1378
value = self._config.get_option('default_stack_on')
1384
class TransportConfig(object):
1385
"""A Config that reads/writes a config file on a Transport.
1387
It is a low-level object that considers config data to be name/value pairs
1388
that may be associated with a section. Assigning meaning to the these
1389
values is done at higher levels like TreeConfig.
1392
def __init__(self, transport, filename):
1393
self._transport = transport
1394
self._filename = filename
1396
def get_option(self, name, section=None, default=None):
1397
"""Return the value associated with a named option.
1399
:param name: The name of the value
1400
:param section: The section the option is in (if any)
1401
:param default: The value to return if the value is not set
1402
:return: The value or default value
1404
configobj = self._get_configobj()
1406
section_obj = configobj
1409
section_obj = configobj[section]
1412
return section_obj.get(name, default)
1414
def set_option(self, value, name, section=None):
1415
"""Set the value associated with a named option.
1417
:param value: The value to set
1418
:param name: The name of the value to set
1419
:param section: The section the option is in (if any)
1421
configobj = self._get_configobj()
1423
configobj[name] = value
1425
configobj.setdefault(section, {})[name] = value
1426
self._set_configobj(configobj)
1428
def _get_config_file(self):
1430
return self._transport.get(self._filename)
1431
except errors.NoSuchFile:
1434
def _get_configobj(self):
1435
return ConfigObj(self._get_config_file(), encoding='utf-8')
1437
def _set_configobj(self, configobj):
1438
out_file = StringIO()
1439
configobj.write(out_file)
1441
self._transport.put_file(self._filename, out_file)