add per user api keys to the backend (#995)
Adds in per user api keys to the backend of lemur. the basics are: - API Keys are really just JWTs with custom second length TTLs. - API Keys are provided in the exact same ways JWTs are now. - API Keys can be revoked/unrevoked at any time by their creator as well as have their TTL Change at anytime. - Users can create/view/list their own API Keys at will, and an admin role has permission to modify all api keys in the instance. Adds in support for lemur api keys to the frontend of lemur. doing this required a few changes to the backend as well, but it is now all working (maybe not the best way though, review will determine that). - fixes inconsistency in moduleauthor name I inputted during the first commit. - Allows the revoke schema to optionally allow a full api_key object. - Adds `/users/:user_id/api_keys/:api_key` and `/users/:user_id/api_keys` endpoints. - normalizes use of `userId` vs `userId` - makes `put` call respond with a JWT so the frontend can show the token on updating. - adds in the API Key views for clicking "API Keys" on the main nav. - adds in the API Key views for clicking into a users edit page. - adds tests for the API Key backend views I added.
This commit is contained in:
@ -33,6 +33,11 @@ class CertificatePermission(Permission):
|
||||
super(CertificatePermission, self).__init__(*needs)
|
||||
|
||||
|
||||
class ApiKeyCreatorPermission(Permission):
|
||||
def __init__(self):
|
||||
super(ApiKeyCreatorPermission, self).__init__(RoleNeed('admin'))
|
||||
|
||||
|
||||
RoleMember = namedtuple('role', ['method', 'value'])
|
||||
RoleMemberNeed = partial(RoleMember, 'member')
|
||||
|
||||
|
@ -27,6 +27,7 @@ from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicNumbers
|
||||
|
||||
from lemur.users import service as user_service
|
||||
from lemur.api_keys import service as api_key_service
|
||||
from lemur.auth.permissions import AuthorityCreatorNeed, RoleMemberNeed
|
||||
|
||||
|
||||
@ -48,9 +49,9 @@ def get_rsa_public_key(n, e):
|
||||
)
|
||||
|
||||
|
||||
def create_token(user):
|
||||
def create_token(user, aid=None, ttl=None):
|
||||
"""
|
||||
Create a valid JWT for a given user, this token is then used to authenticate
|
||||
Create a valid JWT for a given user/api key, this token is then used to authenticate
|
||||
sessions until the token expires.
|
||||
|
||||
:param user:
|
||||
@ -58,10 +59,24 @@ def create_token(user):
|
||||
"""
|
||||
expiration_delta = timedelta(days=int(current_app.config.get('LEMUR_TOKEN_EXPIRATION', 1)))
|
||||
payload = {
|
||||
'sub': user.id,
|
||||
'iat': datetime.utcnow(),
|
||||
'exp': datetime.utcnow() + expiration_delta
|
||||
}
|
||||
|
||||
# Handle Just a User ID & User Object.
|
||||
if isinstance(user, int):
|
||||
payload['sub'] = user
|
||||
else:
|
||||
payload['sub'] = user.id
|
||||
if aid is not None:
|
||||
payload['aid'] = aid
|
||||
# Custom TTLs are only supported on Access Keys.
|
||||
if ttl is not None and aid is not None:
|
||||
# Tokens that are forever until revoked.
|
||||
if ttl == -1:
|
||||
del payload['exp']
|
||||
else:
|
||||
payload['exp'] = ttl
|
||||
token = jwt.encode(payload, current_app.config['LEMUR_TOKEN_SECRET'])
|
||||
return token.decode('unicode_escape')
|
||||
|
||||
@ -94,6 +109,16 @@ def login_required(f):
|
||||
except jwt.InvalidTokenError:
|
||||
return dict(message='Token is invalid'), 403
|
||||
|
||||
if 'aid' in payload:
|
||||
access_key = api_key_service.get(payload['aid'])
|
||||
if access_key.revoked:
|
||||
return dict(message='Token has been revoked'), 403
|
||||
if access_key.ttl != -1:
|
||||
current_time = datetime.utcnow()
|
||||
expired_time = datetime.fromtimestamp(access_key.issued_at + access_key.ttl)
|
||||
if current_time >= expired_time:
|
||||
return dict(message='Token has expired'), 403
|
||||
|
||||
user = user_service.get(payload['sub'])
|
||||
|
||||
if not user.active:
|
||||
|
Reference in New Issue
Block a user