import datetime
from .model_base import AccessControlledModel
from girder.constants import AccessType, TokenScope
from girder.exceptions import ValidationException
from girder.settings import SettingKey
from girder.utility import genToken
[docs]class ApiKey(AccessControlledModel):
"""
This model represents API keys corresponding to users.
"""
[docs] def initialize(self):
self.name = 'api_key'
self.ensureIndices(('userId', 'key'))
self.exposeFields(level=AccessType.READ, fields={
'_id', 'active', 'created', 'key', 'lastUse', 'name', 'scope',
'tokenDuration', 'userId'
})
[docs] def validate(self, doc):
from .token import Token
from .user import User
if doc['tokenDuration']:
doc['tokenDuration'] = float(doc['tokenDuration'])
else:
doc['tokenDuration'] = None
doc['name'] = doc['name'].strip()
doc['active'] = bool(doc.get('active', True))
if doc['scope'] is not None:
if not isinstance(doc['scope'], (list, tuple)):
raise ValidationException('Scope must be a list, or None.')
if not doc['scope']:
raise ValidationException('Custom scope list must not be empty.')
# Ensure only registered scopes are being set
admin = User().load(doc['userId'], force=True)['admin']
scopes = TokenScope.scopeIds(admin)
unknownScopes = set(doc['scope']) - scopes
if unknownScopes:
raise ValidationException('Invalid scopes: %s.' % ','.join(unknownScopes))
# Deactivating an already existing token
if '_id' in doc and not doc['active']:
Token().clearForApiKey(doc)
return doc
[docs] def remove(self, doc):
# Clear tokens corresponding to this API key.
from .token import Token
Token().clearForApiKey(doc)
super().remove(doc)
[docs] def list(self, user, limit=0, offset=0, sort=None):
"""
List API keys for a given user.
:param user: The user whose keys to list.
:type user: dict
:param limit: Result limit.
:param offset: Result offset.
:param sort: The sort structure to pass to pymongo.
:rtype: iterable of API keys for the user.
"""
return self.find({
'userId': user['_id']
}, limit=limit, offset=offset, sort=sort)
[docs] def createApiKey(self, user, name, scope=None, days=None, active=True):
"""
Create a new API key for a user.
:param user: The user who owns the API key.
:type user: dict
:param name: A human readable name for the API key
:param days: The lifespan of the session in days. If not passed, uses
the database setting for cookie lifetime. Note that this is a
maximum duration; clients may request tokens with a shorter lifetime
than this value.
:type days: float or int
:param scope: Scope or list of scopes this API key grants. By default,
will grant tokens provided full access on behalf of the user.
:type scope: str, list of str, or set of str
:param active: Whether this key is active.
:returns: The API key document that was created.
"""
apiKey = {
'created': datetime.datetime.utcnow(),
'lastUse': None,
'tokenDuration': days,
'name': name,
'scope': scope,
'userId': user['_id'],
'key': genToken(40),
'active': active
}
return self.setUserAccess(apiKey, user, level=AccessType.ADMIN, save=True)
[docs] def createToken(self, key, days=None):
"""
Create a token using an API key.
:param key: The API key (the key itself, not the full document).
:type key: str
:param days: You may request a token duration up to the token duration
of the API key itself, or pass None to use the API key duration.
:type days: float or None
"""
from .setting import Setting
from .token import Token
from .user import User
apiKey = self.findOne({
'key': key
})
if apiKey is None or not apiKey['active']:
raise ValidationException('Invalid API key.')
cap = apiKey['tokenDuration'] or Setting().get(SettingKey.COOKIE_LIFETIME)
days = min(float(days or cap), cap)
user = User().load(apiKey['userId'], force=True)
# Mark last used stamp
apiKey['lastUse'] = datetime.datetime.utcnow()
apiKey = self.save(apiKey)
token = Token().createToken(user=user, days=days, scope=apiKey['scope'], apiKey=apiKey)
return (user, token)