Exemple #1
0
def create_perm():
    try:
        perm = Permission.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    if perm.match in ['admin', 'user']:
        raise ApiError('{} role already exists'.format(perm.match), 409)

    for want_scope in perm.scopes:
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                want_scope, ','.join(g.scopes)), 403)

    try:
        perm = perm.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    admin_audit_trail.send(current_app._get_current_object(), event='permission-created', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request)

    if perm:
        return jsonify(status='ok', id=perm.id, permission=perm.serialize), 201
    else:
        raise ApiError('create API key failed', 500)
Exemple #2
0
def list_perms():
    perms = Permission.find_all()

    # add system-defined roles 'admin' and 'user'
    admin_perm = Permission(
        match='admin',
        scopes=[Scope.admin]
    )
    perms.append(admin_perm)

    user_perm = Permission(
        match='user',
        scopes=current_app.config['USER_DEFAULT_SCOPES']
    )
    perms.append(user_perm)

    if perms:
        return jsonify(
            status='ok',
            permissions=[perm.serialize for perm in perms],
            total=len(perms)
        )
    else:
        return jsonify(
            status='ok',
            message='not found',
            permissions=[],
            total=0
        )
Exemple #3
0
def list_perms():
    query = qb.from_params(request.args)
    perms = Permission.find_all(query)

    admin_perm = Permission(match=DEFAULT_ADMIN_ROLE, scopes=[Scope.admin])
    user_perm = Permission(match='user',
                           scopes=current_app.config['USER_DEFAULT_SCOPES'])
    guest_perm = Permission(match='guest',
                            scopes=current_app.config['GUEST_DEFAULT_SCOPES'])

    # add system-defined roles 'admin', 'user' and 'guest
    if 'scopes' in request.args:
        want_scopes = request.args.getlist('scopes')
        if set(admin_perm.scopes) & set(want_scopes):
            perms.append(admin_perm)
        if set(user_perm.scopes) & set(want_scopes):
            perms.append(user_perm)
        if set(guest_perm.scopes) & set(want_scopes):
            perms.append(guest_perm)
    else:
        perms.append(admin_perm)
        perms.append(user_perm)
        perms.append(guest_perm)

    if perms:
        return jsonify(status='ok',
                       permissions=[perm.serialize for perm in perms],
                       total=len(perms))
    else:
        return jsonify(status='ok',
                       message='not found',
                       permissions=[],
                       total=0)
Exemple #4
0
def update_user(user_id):
    if not request.json:
        raise ApiError('nothing to change', 400)

    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if request.json.get('email'):
        user_by_email = User.find_by_email(request.json['email'])
        if user_by_email and user_by_email.id != user.id:
            raise ApiError('user with that email already exists', 409)

    if request.json.get('roles'):
        want_scopes = Permission.lookup(login='', roles=request.json['roles'])
        for want_scope in want_scopes:
            if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
                raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                    want_scope, ','.join(g.scopes)), 403)

    admin_audit_trail.send(current_app._get_current_object(), event='user-updated', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request)

    if user.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update user', 500)
Exemple #5
0
    def test_system_roles(self):

        login = '******'
        roles = ['no-ops', 'team']

        with self.app.test_request_context():
            scopes = Permission.lookup(login, roles)
        self.assertEqual(scopes, [Scope.admin, Scope.read, Scope.write])

        login = '******'
        roles = ['web', 'ops']

        with self.app.test_request_context():
            scopes = Permission.lookup(login, roles)
        self.assertEqual(scopes, [Scope.admin, Scope.read, Scope.write])

        login = '******'
        roles = ['dev', 'engineer']

        with self.app.test_request_context():
            scopes = Permission.lookup(login, roles)
        self.assertEqual(scopes, [Scope.read, Scope.write])

        login = '******'
        roles = ['guest']

        with self.app.test_request_context():
            scopes = Permission.lookup(login, roles)
        self.assertEqual(scopes, [Scope.read_alerts])
Exemple #6
0
def update_user(user_id):
    if not request.json:
        raise ApiError('nothing to change', 400)

    user = User.find_by_id(user_id)

    if not user:
        raise ApiError('not found', 404)

    if request.json.get('email'):
        user_by_email = User.find_by_email(request.json['email'])
        if user_by_email and user_by_email.id != user.id:
            raise ApiError('user with that email already exists', 409)

    if request.json.get('roles'):
        want_scopes = Permission.lookup(login='', roles=request.json['roles'])
        for want_scope in want_scopes:
            if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
                raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                    want_scope, ','.join(g.scopes)), 403)

    updated = user.update(**request.json)

    admin_audit_trail.send(current_app._get_current_object(), event='user-updated', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request)

    if updated:
        return jsonify(status='ok', user=updated.serialize)
    else:
        raise ApiError('failed to update user', 500)
Exemple #7
0
def create_perm():
    try:
        perm = Permission.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    if perm.match in ['admin', 'user']:
        raise ApiError('{} role already exists'.format(perm.match), 409)

    for want_scope in perm.scopes:
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                want_scope, ','.join(g.scopes)), 403)

    try:
        perm = perm.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    admin_audit_trail.send(current_app._get_current_object(), event='permission-created', message='', user=g.user,
                           customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request)

    if perm:
        return jsonify(status='ok', id=perm.id, permission=perm.serialize), 201
    else:
        raise ApiError('create API key failed', 500)
Exemple #8
0
def list_perms():

    query = qb.perms.from_params(request.args)
    total = Permission.count(query)
    perms: list[Permission] = []

    admin_perm = Permission(match=current_app.config['DEFAULT_ADMIN_ROLE'],
                            scopes=ADMIN_SCOPES)
    user_perm = Permission(match=current_app.config['DEFAULT_USER_ROLE'],
                           scopes=current_app.config['USER_DEFAULT_SCOPES'])
    guest_perm = Permission(match=current_app.config['DEFAULT_GUEST_ROLE'],
                            scopes=current_app.config['GUEST_DEFAULT_SCOPES'])

    # add system-defined roles 'admin', 'user' and 'guest
    if 'scopes' in request.args:
        want_scopes = request.args.getlist('scopes')
        if set(admin_perm.scopes) & set(want_scopes):
            perms.append(admin_perm)
            total += 1
        if set(user_perm.scopes) & set(want_scopes):
            perms.append(user_perm)
            total += 1
        if set(guest_perm.scopes) & set(want_scopes):
            perms.append(guest_perm)
            total += 1
    else:
        perms.append(admin_perm)
        perms.append(user_perm)
        perms.append(guest_perm)
        total += 3

    paging = Page.from_params(request.args, total)
    perms.extend(
        Permission.find_all(query,
                            page=paging.page,
                            page_size=paging.page_size))

    if perms:
        return jsonify(status='ok',
                       page=paging.page,
                       pageSize=paging.page_size,
                       pages=paging.pages,
                       more=paging.has_more,
                       permissions=[perm.serialize for perm in perms],
                       total=total)
    else:
        return jsonify(status='ok',
                       page=paging.page,
                       pageSize=paging.page_size,
                       pages=paging.pages,
                       more=paging.has_more,
                       message='not found',
                       permissions=[],
                       total=0)
Exemple #9
0
        def wrapped(*args, **kwargs):

            auth_header = request.headers.get('Authorization', '')
            m = re.match(r'Key (\S+)', auth_header)
            param = m.group(1) if m else request.args.get('api-key', None)

            if param:
                key = ApiKey.verify_key(param)
                if not key:
                    raise ApiError("API key parameter '%s' is invalid" % param, 401)
                g.user = key.user
                g.customer = key.customer
                g.scopes = key.scopes

                if not Permission.is_in_scope(scope, g.scopes):
                    raise ApiError('Missing required scope: %s' % scope, 403)
                else:
                    return f(*args, **kwargs)

            auth_header = request.headers.get('Authorization', '')
            m = re.match(r'Bearer (\S+)', auth_header)
            token = m.group(1) if m else None

            if token:
                try:
                    jwt = Jwt.parse(token)
                except DecodeError:
                    raise ApiError('Token is invalid', 401)
                except ExpiredSignature:
                    raise ApiError('Token has expired', 401)
                except InvalidAudience:
                    raise ApiError('Invalid audience', 401)
                g.user = jwt.preferred_username
                g.customer = jwt.customer
                g.scopes = jwt.scopes

                if not Permission.is_in_scope(scope, g.scopes):
                    raise ApiError("Missing required scope: %s" % scope, 403)
                else:
                    return f(*args, **kwargs)

            if not current_app.config['AUTH_REQUIRED']:
                g.user = None
                g.customer = None
                g.scopes = []
                return f(*args, **kwargs)

            # Google App Engine Cron Service
            if request.headers.get('X-Appengine-Cron', False) and request.headers.get('X-Forwarded-For', '') == '0.1.0.1':
                return f(*args, **kwargs)

            raise ApiError('Missing authorization API Key or Bearer Token', 401)
Exemple #10
0
    def test_system_roles(self):

        login = '******'
        roles = ['admin']

        with self.app.test_request_context():
            scopes = Permission.lookup(login, roles)
        self.assertEqual(scopes, [Scope.admin, Scope.read, Scope.write])

        login = '******'
        roles = ['user']

        with self.app.test_request_context():
            scopes = Permission.lookup(login, roles)
        self.assertEqual(scopes, [Scope.read, Scope.write])
Exemple #11
0
def create_user():
    if current_app.config['AUTH_PROVIDER'] != 'basic':
        raise ApiError(
            'must use {} login flow to create new user'.format(
                current_app.config['AUTH_PROVIDER']), 400)

    try:
        user = User.parse(request.json)
    except Exception as e:
        raise ApiError(str(e), 400)

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    if User.find_by_username(username=user.email):
        raise ApiError('user with that email already exists', 409)

    want_scopes = Permission.lookup(login=user.email, roles=user.roles)
    for want_scope in want_scopes:
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError(
                "Requested scope '{}' not in existing scopes: {}".format(
                    want_scope, ','.join(g.scopes)), 403)

    try:
        user = user.create()
    except Exception as e:
        ApiError(str(e), 500)

    # if email verification is enforced, send confirmation email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()

    admin_audit_trail.send(current_app._get_current_object(),
                           event='user-created',
                           message='',
                           user=g.login,
                           customers=g.customers,
                           scopes=g.scopes,
                           resource_id=user.id,
                           type='user',
                           request=request)

    if user:
        return jsonify(status='ok', id=user.id, user=user.serialize), 201
    else:
        raise ApiError('create user failed', 500)
Exemple #12
0
def signup():

    if not current_app.config['SIGNUP_ENABLED']:
        raise ApiError('user signup is disabled', 403)

    try:
        user = User.parse(request.json)
    except Exception as e:
        raise ApiError(str(e), 400)

    # set sign-up defaults
    user.roles = ['user']
    user.email_verified = False

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    if User.find_by_email(email=user.email):
        raise ApiError('username already exists', 409)

    try:
        user = user.create()
    except Exception as e:
        ApiError(str(e), 500)

    # if email verification is enforced, deny login and send email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()
        raise ApiError('email not verified', 403)

    # check user is active & update last login
    if user.status != 'active':
        raise ApiError('user not active', 403)
    user.update_last_login()

    # assign customers
    customers = get_customers(user.email, groups=[user.domain])

    auth_audit_trail.send(current_app._get_current_object(),
                          event='basic-auth-signup',
                          message='user signup using BasicAuth',
                          user=user.email,
                          customers=customers,
                          scopes=Permission.lookup(user.email,
                                                   groups=user.roles),
                          resource_id=user.id,
                          type='user',
                          request=request)

    # generate token
    token = create_token(user_id=user.id,
                         name=user.name,
                         login=user.email,
                         provider='basic',
                         customers=customers,
                         roles=user.roles,
                         email=user.email,
                         email_verified=user.email_verified)
    return jsonify(token=token.tokenize)
Exemple #13
0
def create_key():
    try:
        key = ApiKey.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    if 'admin' in g.scopes or 'admin:keys' in g.scopes:
        key.user = key.user or g.user
        key.customer = key.customer or g.get('customer', None)
    else:
        key.user = g.user
        key.customer = g.get('customer', None)

    if not key.user:
        raise ApiError(
            "An API key must be associated with a 'user'. Retry with user credentials.",
            400)

    for want_scope in key.scopes:
        if not Permission.is_in_scope(want_scope, g.scopes):
            raise ApiError(
                "Requested scope '%s' not in existing scopes: %s" %
                (want_scope, ','.join(g.scopes)), 403)

    try:
        key = key.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    if key:
        return jsonify(status="ok", key=key.key, data=key.serialize), 201
    else:
        raise ApiError("create API key failed", 500)
Exemple #14
0
def get_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if perm:
        return jsonify(status='ok', total=1, permission=perm.serialize)
    else:
        raise ApiError('not found', 404)
Exemple #15
0
def create_token(user_id: str,
                 name: str,
                 login: str,
                 provider: str,
                 customers: List[str],
                 orgs: List[str] = None,
                 groups: List[str] = None,
                 roles: List[str] = None,
                 email: str = None,
                 email_verified: bool = None) -> 'Jwt':
    now = datetime.utcnow()
    scopes = Permission.lookup(login,
                               groups=(roles or []) + (groups or []) +
                               (orgs or []))
    return Jwt(iss=request.url_root,
               typ='Bearer',
               sub=user_id,
               aud=current_app.config.get('OAUTH2_CLIENT_ID', None)
               or request.url_root,
               exp=(now +
                    timedelta(days=current_app.config['TOKEN_EXPIRE_DAYS'])),
               nbf=now,
               iat=now,
               jti=str(uuid4()),
               name=name,
               preferred_username=login,
               orgs=orgs,
               roles=roles,
               groups=groups,
               provider=provider,
               scopes=scopes,
               email=email,
               email_verified=email_verified,
               customers=customers)
Exemple #16
0
def update_perm(perm_id):
    if not request.json:
        raise ApiError('nothing to change', 400)

    for s in request.json.get('scopes', []):
        if s not in list(Scope):
            raise ApiError("'{}' is not a valid Scope".format(s), 400)

    perm = Permission.find_by_id(perm_id)

    if not perm:
        raise ApiError('not found', 404)

    admin_audit_trail.send(current_app._get_current_object(),
                           event='permission-updated',
                           message='',
                           user=g.login,
                           customers=g.customers,
                           scopes=g.scopes,
                           resource_id=perm.id,
                           type='permission',
                           request=request)

    if perm.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update permission', 500)
Exemple #17
0
def create_key():
    try:
        key = ApiKey.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    if Scope.admin in g.scopes or Scope.admin_keys in g.scopes:
        key.user = key.user or g.login
    else:
        key.user = g.login

    key.customer = assign_customer(wanted=key.customer, permission=Scope.admin_keys)

    if not key.user:
        raise ApiError("An API key must be associated with a 'user'. Retry with user credentials.", 400)

    for want_scope in key.scopes:
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                want_scope, ','.join(g.scopes)), 403)

    try:
        key = key.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    write_audit_trail.send(current_app._get_current_object(), event='apikey-created', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=key.id, type='apikey', request=request)

    if key:
        return jsonify(status='ok', key=key.key, data=key.serialize), 201
    else:
        raise ApiError('create API key failed', 500)
Exemple #18
0
def update_key(key):
    if not request.json:
        raise ApiError('nothing to change', 400)

    if not current_app.config['AUTH_REQUIRED']:
        key = ApiKey.find_by_id(key)
    elif Scope.admin in g.scopes or Scope.admin_keys in g.scopes:
        key = ApiKey.find_by_id(key)
    else:
        key = ApiKey.find_by_id(key, user=g.login)

    if not key:
        raise ApiError('not found', 404)

    update = request.json
    update['customer'] = assign_customer(wanted=update.get('customer'), permission=Scope.admin_keys)

    for want_scope in update.get('scopes', []):
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                want_scope, ','.join(g.scopes)), 403)

    admin_audit_trail.send(current_app._get_current_object(), event='apikey-updated', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=key.id, type='apikey', request=request)

    if key.update(**request.json):
        return jsonify(status='ok')
    else:
        raise ApiError('failed to update API key', 500)
Exemple #19
0
def pingfederate():

    access_token_url = current_app.config['PINGFEDERATE_OPENID_ACCESS_TOKEN_URL']

    payload = {
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
        'redirect_uri': request.json['redirectUri'],
        'grant_type': 'authorization_code',
        'code': request.json['code'],
        'scope': 'openid email',
    }

    try:
        r = requests.post(access_token_url, data=payload)
    except Exception:
        raise ApiError('Failed to call sso API over HTTPS', 400)
    access_token = r.json()
    encoded = access_token['access_token']

    keyfile = open(current_app.config['PINGFEDERATE_PUBKEY_LOCATION'], 'r')
    keystring = keyfile.read()

    decoded = jwt.decode(encoded, keystring, algorithms=current_app.config['PINGFEDERATE_TOKEN_ALGORITHM'])

    login = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_USERNAME']]
    email = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_EMAIL']]
    customers = get_customers(login, current_app.config['PINGFEDERATE_OPENID_PAYLOAD_GROUP'])

    auth_audit_trail.send(current_app._get_current_object(), event='pingfederate-login', message='user login via PingFederate',
                          user=email, customers=customers, scopes=Permission.lookup(login, groups=[]),
                          resource_id=login, type='pingfederate', request=request)

    token = create_token(user_id=login, name=email, login=email, provider='openid', customers=customers)
    return jsonify(token=token.tokenize)
Exemple #20
0
def create_key():
    try:
        key = ApiKey.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    if 'admin' in g.scopes or 'admin:keys' in g.scopes:
        key.user = key.user or g.user
        key.customer = key.customer or g.get('customer', None)
    else:
        key.user = g.user
        key.customer = g.get('customer', None)

    if not key.user:
        raise ApiError("Must set 'user' to create API key", 400)

    for want_scope in key.scopes:
        if not Permission.is_in_scope(want_scope, g.scopes):
            raise ApiError("Requested scope '%s' not in existing scopes: %s" % (want_scope, ','.join(g.scopes)), 403)

    try:
        key = key.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    if key:
        return jsonify(status="ok", key=key.key, data=key.serialize), 201
    else:
        raise ApiError("create API key failed", 500)
Exemple #21
0
def pingfederate():

    access_token_url = current_app.config['PINGFEDERATE_OPENID_ACCESS_TOKEN_URL']

    data = {
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
        'redirect_uri': request.json['redirectUri'],
        'grant_type': 'authorization_code',
        'code': request.json['code'],
        'scope': 'openid email',
    }

    r = requests.post(access_token_url, data)
    access_token = r.json()
    encoded = access_token['access_token']

    keyfile = open(current_app.config['PINGFEDERATE_PUBKEY_LOCATION'], 'r')
    keystring = keyfile.read()

    decoded = jwt.decode(encoded, keystring, algorithms=current_app.config['PINGFEDERATE_TOKEN_ALGORITHM'])

    login = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_USERNAME']]
    email = decoded[current_app.config['PINGFEDERATE_OPENID_PAYLOAD_EMAIL']]

    scopes = Permission.lookup(login, roles=[])
    customers = get_customers(login, current_app.config['PINGFEDERATE_OPENID_PAYLOAD_GROUP'])

    auth_audit_trail.send(current_app._get_current_object(), event='pingfederate-login', message='user login via PingFederate',
                          user=email, customers=customers, scopes=scopes,
                          resource_id=login, type='pingfederate', request=request)

    token = create_token(user_id=login, name=email, login=email, provider='openid', customers=customers, scopes=scopes)
    return jsonify(token=token.tokenize)
Exemple #22
0
def get_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if perm:
        return jsonify(status='ok', total=1, permission=perm.serialize)
    else:
        raise ApiError('not found', 404)
Exemple #23
0
def github():

    if current_app.config['GITHUB_URL']:
        access_token_url = current_app.config[
            'GITHUB_URL'] + '/login/oauth/access_token'
        github_api_url = current_app.config['GITHUB_URL'] + '/api/v3'
    else:
        access_token_url = 'https://github.com/login/oauth/access_token'
        github_api_url = 'https://api.github.com'

    client_lookup = dict(
        zip(current_app.config['OAUTH2_CLIENT_ID'].split(','),
            current_app.config['OAUTH2_CLIENT_SECRET'].split(',')))
    client_secret = client_lookup.get(request.json['clientId'], None)
    params = {
        'client_id': request.json['clientId'],
        'redirect_uri': request.json['redirectUri'],
        'client_secret': client_secret,
        'code': request.json['code']
    }

    headers = {'Accept': 'application/json'}
    r = requests.get(access_token_url, headers=headers, params=params)
    access_token = r.json()

    r = requests.get(github_api_url + '/user', params=access_token)
    profile = r.json()

    r = requests.get(
        github_api_url + '/user/orgs',
        params=access_token)  # list public and private Github orgs
    organizations = [o['login'] for o in r.json()]
    login = profile['login']

    if not_authorized('ALLOWED_GITHUB_ORGS', organizations):
        raise ApiError('User %s is not authorized' % login, 403)

    customers = get_customers(login, organizations)

    auth_audit_trail.send(current_app._get_current_object(),
                          event='github-login',
                          message='user login via GitHub',
                          user=login,
                          customers=customers,
                          scopes=Permission.lookup(login,
                                                   groups=organizations),
                          resource_id=profile['id'],
                          type='github',
                          request=request)

    token = create_token(user_id=profile['id'],
                         name=profile.get('name', '@' + login),
                         login=login,
                         provider='github',
                         customers=customers,
                         orgs=organizations,
                         email=profile.get('email', None),
                         email_verified=True if 'email' in profile else False)
    return jsonify(token=token.tokenize)
Exemple #24
0
def gitlab():

    access_token_url = current_app.config['GITLAB_URL'] + '/oauth/token'
    tokeninfo_url = current_app.config['GITLAB_URL'] + '/oauth/token/info'
    userinfo_url = current_app.config['GITLAB_URL'] + '/oauth/userinfo'
    gitlab_api_url = current_app.config['GITLAB_URL'] + '/api/v4'

    payload = {
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
        'redirect_uri': request.json['redirectUri'],
        'grant_type': 'authorization_code',
        'code': request.json['code'],
    }

    try:
        r = requests.post(access_token_url, data=payload)
    except Exception:
        raise ApiError('Failed to call GitLab API over HTTPS', 400)
    token = r.json()

    headers = {'Authorization': 'Bearer ' + token['access_token']}
    r = requests.get(tokeninfo_url, headers=headers)
    scopes = r.json().get('scopes', [])
    current_app.logger.info('GitLab scopes: {}'.format(scopes))

    if 'openid' in scopes:
        r = requests.post(userinfo_url, headers=headers)
        profile = r.json()

        user_id = profile['sub']
        login = profile['nickname']
        groups = profile.get('groups', [])
        email_verified = profile.get('email_verified', False)
    else:
        r = requests.get(gitlab_api_url + '/user', headers=headers)
        profile = r.json()

        user_id = profile['id']
        login = profile['username']

        r = requests.get(gitlab_api_url + '/groups', headers=headers)
        groups = [g['full_path'] for g in r.json()]
        email_verified = True if profile.get('email', None) else False

    if not_authorized('ALLOWED_GITLAB_GROUPS', groups):
        raise ApiError('User %s is not authorized' % login, 403)

    customers = get_customers(login, groups)

    auth_audit_trail.send(current_app._get_current_object(), event='gitlab-login', message='user login via GitLab',
                          user=login, customers=customers, scopes=Permission.lookup(login, groups=groups),
                          resource_id=user_id, type='gitlab', request=request)

    token = create_token(user_id=user_id, name=profile.get('name', '@' + login), login=login, provider='gitlab',
                         customers=customers, groups=groups, email=profile.get('email', None), email_verified=email_verified)
    return jsonify(token=token.tokenize)
Exemple #25
0
def delete_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if not perm:
        raise ApiError("not found", 404)

    if perm.delete():
        return jsonify(status="ok")
    else:
        raise ApiError("failed to delete permission", 500)
Exemple #26
0
def create_perm():
    try:
        perm = Permission.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    for want_scope in perm.scopes:
        if not Permission.is_in_scope(want_scope, g.scopes):
            raise ApiError("Requested scope '%s' not in existing scopes: %s" % (want_scope, ','.join(g.scopes)), 403)

    try:
        perm = perm.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    if perm:
        return jsonify(status="ok", id=perm.id, permission=perm.serialize), 201
    else:
        raise ApiError("create API key failed", 500)
Exemple #27
0
def create_perm():
    try:
        perm = Permission.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    for want_scope in perm.scopes:
        if not Permission.is_in_scope(want_scope, g.scopes):
            raise ApiError("Requested scope '%s' not in existing scopes: %s" % (want_scope, ','.join(g.scopes)), 403)

    try:
        perm = perm.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    if perm:
        return jsonify(status="ok", id=perm.id, permission=perm.serialize), 201
    else:
        raise ApiError("create API key failed", 500)
Exemple #28
0
def delete_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if not perm:
        raise ApiError("not found", 404)

    if perm.delete():
        return jsonify(status="ok")
    else:
        raise ApiError("failed to delete permission", 500)
Exemple #29
0
def delete_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if not perm:
        raise ApiError('not found', 404)

    if perm.delete():
        return jsonify(status='ok')
    else:
        raise ApiError('failed to delete permission', 500)
Exemple #30
0
def google():
    access_token_url = 'https://accounts.google.com/o/oauth2/token'
    people_api_url = 'https://www.googleapis.com/plus/v1/people/me/openIdConnect'

    payload = {
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
        'redirect_uri': request.json['redirectUri'],
        'grant_type': 'authorization_code',
        'code': request.json['code'],
    }
    r = requests.post(access_token_url, data=payload)
    token = r.json()

    id_token = Jwt.parse(token['id_token'],
                         key='',
                         verify=False,
                         algorithm='RS256')

    domain = id_token.email.split('@')[1]

    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[domain]):
        raise ApiError('User %s is not authorized' % id_token.email, 403)

    # Get Google+ profile for Full name
    headers = {'Authorization': 'Bearer ' + token['access_token']}
    r = requests.get(people_api_url, headers=headers)
    profile = r.json()

    if not profile:
        raise ApiError('Google+ API is not enabled for this Client ID', 400)

    customers = get_customers(id_token.email, groups=[domain])
    name = profile.get('name', id_token.email.split('@')[0])

    auth_audit_trail.send(current_app._get_current_object(),
                          event='google-login',
                          message='user login via Google',
                          user=id_token.email,
                          customers=customers,
                          scopes=Permission.lookup(id_token.email,
                                                   groups=[domain]),
                          resource_id=id_token.subject,
                          type='google',
                          request=request)

    token = create_token(user_id=id_token.subject,
                         name=name,
                         login=id_token.email,
                         provider='google',
                         customers=customers,
                         orgs=[domain],
                         email=id_token.email,
                         email_verified=id_token.email_verified)
    return jsonify(token=token.tokenize)
Exemple #31
0
def login():
    # lookup user from username/email
    try:
        username = request.json.get('username', None) or request.json['email']
        password = request.json['password']
    except KeyError:
        raise ApiError("must supply 'username' and 'password'", 401)

    user = User.check_credentials(username, password)
    if not user:
        raise ApiError('Invalid username or password', 401)

    # if email verification is enforced, deny login and send email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()
        raise ApiError('email not verified', 403)

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    # check user is active & update last login
    if user.status != 'active':
        raise ApiError('User {} not active'.format(user.login), 403)
    user.update_last_login()

    groups = [g.name for g in user.get_groups()]
    scopes = Permission.lookup(login=user.login, roles=user.roles + groups)
    customers = get_customers(login=user.login, groups=[user.domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(),
                          event='basic-auth-login',
                          message='user login via BasicAuth',
                          user=user.login,
                          customers=customers,
                          scopes=scopes,
                          roles=user.roles,
                          groups=groups,
                          resource_id=user.id,
                          type='user',
                          request=request)

    # generate token
    token = create_token(user_id=user.id,
                         name=user.name,
                         login=user.login,
                         provider='basic',
                         customers=customers,
                         scopes=scopes,
                         roles=user.roles,
                         groups=groups,
                         email=user.email,
                         email_verified=user.email_verified)
    return jsonify(token=token.tokenize)
Exemple #32
0
def list_perms():
    perms = Permission.find_all()

    if perms:
        return jsonify(status='ok',
                       permissions=[perm.serialize for perm in perms],
                       total=len(perms))
    else:
        return jsonify(status='ok',
                       message='not found',
                       permissions=[],
                       total=0)
Exemple #33
0
def saml_response_from_idp():
    def _make_response(resp_obj, resp_code):
        if 'usePostMessage' in request.form.get('RelayState', '') and 'text/html' in request.headers.get('Accept', ''):
            origins = current_app.config.get('CORS_ORIGINS', [])
            response = make_response(
                '''<!DOCTYPE html>
                    <html lang="en">
                        <head>
                            <meta charset="UTF-8">
                            <title>Authenticating...</title>
                            <script type="application/javascript">
                                var origins = {origins};
                                // in case when API and WebUI are on the same origin
                                if (origins.indexOf(window.location.origin) < 0)
                                    origins.push(window.location.origin);
                                // only one will succeed
                                origins.forEach(origin => window.opener.postMessage({msg_data}, origin));
                                window.close();
                            </script>
                        </head>
                        <body></body>
                    </html>'''.format(msg_data=json.dumps(resp_obj), origins=json.dumps(origins)),
                resp_code
            )
            response.headers['Content-Type'] = 'text/html'
            return response
        else:
            return jsonify(**resp_obj), resp_code

    authn_response = saml_client().parse_authn_request_response(
        request.form['SAMLResponse'],
        saml2.entity.BINDING_HTTP_POST
    )
    identity = authn_response.get_identity()
    email = identity['emailAddress'][0]
    domain = email.split('@')[1]
    name = (current_app.config.get('SAML2_USER_NAME_FORMAT', '{givenName} {surname}')).format(
        **dict(map(lambda x: (x[0], x[1][0]), identity.items())))

    groups = identity.get('groups', [])
    if not_authorized('ALLOWED_SAML2_GROUPS', groups):
        return _make_response({'status': 'error', 'message': 'User {} is not authorized'.format(email)}, 403)

    scopes = Permission.lookup(login=email, roles=groups)
    customers = get_customers(login=email, groups=[domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(), event='saml2-login', message='user login via SAML2',
                          user=email, customers=customers, scopes=scopes,
                          resource_id=email, type='saml2', request=request)

    token = create_token(user_id=email, name=name, login=email, provider='saml2',
                         customers=customers, scopes=scopes, groups=groups)
    return _make_response({'status': 'ok', 'token': token.tokenize}, 200)
Exemple #34
0
def azure():

    if not current_app.config['AZURE_TENANT']:
        raise ApiError(
            'Must define AZURE_TENANT setting in server configuration.', 503)

    discovery_doc_url = 'https://login.microsoftonline.com/{}/.well-known/openid-configuration'.format(
        current_app.config['AZURE_TENANT'])

    r = requests.get(discovery_doc_url)
    token_endpoint = r.json()['token_endpoint']

    data = {
        'code': request.json['code'],
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
        'redirect_uri': request.json['redirectUri'],
        'grant_type': 'authorization_code'
    }
    r = requests.post(token_endpoint, data)
    token = r.json()

    id_token = jwt.decode(token['id_token'], verify=False)

    subject = id_token['sub']
    name = id_token['name']
    email = id_token['email']
    domain = email.split('@')[1]

    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[domain]):
        raise ApiError('User %s is not authorized' % email, 403)

    customers = get_customers(email, groups=[domain])

    auth_audit_trail.send(current_app._get_current_object(),
                          event='azure-login',
                          message='user login via Azure',
                          user=email,
                          customers=customers,
                          scopes=Permission.lookup(email, groups=[domain]),
                          resource_id=subject,
                          type='azure',
                          request=request)

    token = create_token(user_id=subject,
                         name=name,
                         login=email,
                         provider='azure',
                         customers=customers,
                         orgs=[domain],
                         email=email)
    return jsonify(token=token.tokenize)
Exemple #35
0
def delete_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if not perm:
        raise ApiError('not found', 404)

    admin_audit_trail.send(current_app._get_current_object(), event='permission-deleted', message='', user=g.user,
                           customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request)

    if perm.delete():
        return jsonify(status='ok')
    else:
        raise ApiError('failed to delete permission', 500)
Exemple #36
0
def create_user():
    if current_app.config['AUTH_PROVIDER'] != 'basic':
        raise ApiError(
            'must use {} login flow to create new user'.format(current_app.config['AUTH_PROVIDER']), 400)

    try:
        user = User.parse(request.json)
    except Exception as e:
        raise ApiError(str(e), 400)

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    if User.find_by_username(username=user.email):
        raise ApiError('user with that email already exists', 409)

    want_scopes = Permission.lookup(login=user.email, roles=user.roles)
    for want_scope in want_scopes:
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError("Requested scope '{}' not in existing scopes: {}".format(
                want_scope, ','.join(g.scopes)), 403)

    try:
        user = user.create()
    except Exception as e:
        ApiError(str(e), 500)

    # if email verification is enforced, send confirmation email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()

    admin_audit_trail.send(current_app._get_current_object(), event='user-created', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=user.id, type='user', request=request)

    if user:
        return jsonify(status='ok', id=user.id, user=user.serialize), 201
    else:
        raise ApiError('create user failed', 500)
Exemple #37
0
def delete_perm(perm_id):
    perm = Permission.find_by_id(perm_id)

    if not perm:
        raise ApiError('not found', 404)

    admin_audit_trail.send(current_app._get_current_object(), event='permission-deleted', message='', user=g.login,
                           customers=g.customers, scopes=g.scopes, resource_id=perm.id, type='permission', request=request)

    if perm.delete():
        return jsonify(status='ok')
    else:
        raise ApiError('failed to delete permission', 500)
Exemple #38
0
def saml_response_from_idp():
    def _make_response(resp_obj, resp_code):
        if 'usePostMessage' in request.form.get('RelayState', '') and 'text/html' in request.headers.get('Accept', ''):
            origins = current_app.config.get('CORS_ORIGINS', [])
            response = make_response(
                '''<!DOCTYPE html>
                    <html lang="en">
                        <head>
                            <meta charset="UTF-8">
                            <title>Authenticating...</title>
                            <script type="application/javascript">
                                var origins = {origins};
                                // in case when API and WebUI are on the same origin
                                if (origins.indexOf(window.location.origin) < 0)
                                    origins.push(window.location.origin);
                                // only one will succeed
                                origins.forEach(origin => window.opener.postMessage({msg_data}, origin));
                                window.close();
                            </script>
                        </head>
                        <body></body>
                    </html>'''.format(msg_data=json.dumps(resp_obj), origins=json.dumps(origins)),
                resp_code
            )
            response.headers['Content-Type'] = 'text/html'
            return response
        else:
            return jsonify(**resp_obj), resp_code

    authn_response = saml_client().parse_authn_request_response(
        request.form['SAMLResponse'],
        saml2.entity.BINDING_HTTP_POST
    )
    identity = authn_response.get_identity()
    email = identity['emailAddress'][0]
    domain = email.split('@')[1]
    name = (current_app.config.get('SAML2_USER_NAME_FORMAT', '{givenName} {surname}')).format(
        **dict(map(lambda x: (x[0], x[1][0]), identity.items())))

    groups = identity.get('groups', [])
    if not_authorized('ALLOWED_SAML2_GROUPS', groups):
        return _make_response({'status': 'error', 'message': 'User {} is not authorized'.format(email)}, 403)

    customers = get_customers(email, groups=[domain])

    auth_audit_trail.send(current_app._get_current_object(), event='saml2-login', message='user login via SAML2',
                          user=email, customers=customers, scopes=Permission.lookup(email, groups=groups),
                          resource_id=email, type='saml2', request=request)

    token = create_token(user_id=email, name=name, login=email, provider='saml2', customers=customers, groups=groups)
    return _make_response({'status': 'ok', 'token': token.tokenize}, 200)
Exemple #39
0
def signup():

    if not current_app.config['SIGNUP_ENABLED']:
        raise ApiError('user signup is disabled', 403)

    try:
        user = User.parse(request.json)
    except Exception as e:
        raise ApiError(str(e), 400)

    # set sign-up defaults
    user.roles = ['user']
    user.email_verified = False

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    if User.find_by_username(username=user.email):
        raise ApiError('user with that email already exists', 409)

    try:
        user = user.create()
    except Exception as e:
        ApiError(str(e), 500)

    # if email verification is enforced, deny login and send email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()
        raise ApiError('email not verified', 403)

    # check user is active & update last login
    if user.status != 'active':
        raise ApiError('User {} not active'.format(user.login), 403)
    user.update_last_login()

    groups = [g.name for g in user.get_groups()]
    scopes = Permission.lookup(login=user.login, roles=user.roles + groups)
    customers = get_customers(login=user.login, groups=[user.domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(), event='basic-auth-signup', message='user signup using BasicAuth',
                          user=user.login, customers=customers, scopes=scopes,
                          resource_id=user.id, type='user', request=request)

    # generate token
    token = create_token(user_id=user.id, name=user.name, login=user.login, provider='basic',
                         customers=customers, scopes=scopes, roles=user.roles, groups=groups,
                         email=user.email, email_verified=user.email_verified)
    return jsonify(token=token.tokenize)
Exemple #40
0
def list_perms():
    perms = Permission.find_all()

    if perms:
        return jsonify(
            status="ok",
            permissions=[perm.serialize for perm in perms],
            total=len(perms)
        )
    else:
        return jsonify(
            status="ok",
            message="not found",
            permissions=[],
            total=0
        )
Exemple #41
0
def github():

    if current_app.config['GITHUB_URL'] == 'https://github.com':
        access_token_url = 'https://github.com/login/oauth/access_token'
        github_api_url = 'https://api.github.com'
    else:
        access_token_url = current_app.config['GITHUB_URL'] + '/login/oauth/access_token'
        github_api_url = current_app.config['GITHUB_URL'] + '/api/v3'

    client_lookup = dict(zip(
        current_app.config['OAUTH2_CLIENT_ID'].split(','),
        current_app.config['OAUTH2_CLIENT_SECRET'].split(',')
    ))
    client_secret = client_lookup.get(request.json['clientId'], None)
    params = {
        'client_id': request.json['clientId'],
        'redirect_uri': request.json['redirectUri'],
        'client_secret': client_secret,
        'code': request.json['code']
    }

    headers = {'Accept': 'application/json'}
    r = requests.get(access_token_url, headers=headers, params=params)
    access_token = r.json()

    r = requests.get(github_api_url + '/user', params=access_token)
    profile = r.json()

    r = requests.get(github_api_url + '/user/orgs', params=access_token)  # list public and private Github orgs
    organizations = [o['login'] for o in r.json()]
    login = profile['login']

    if not_authorized('ALLOWED_GITHUB_ORGS', organizations):
        raise ApiError('User %s is not authorized' % login, 403)

    scopes = Permission.lookup(login, roles=organizations)
    customers = get_customers(login, groups=organizations)

    auth_audit_trail.send(current_app._get_current_object(), event='github-login', message='user login via GitHub',
                          user=login, customers=customers, scopes=scopes,
                          resource_id=profile['id'], type='github', request=request)

    token = create_token(user_id=profile['id'], name=profile.get('name', '@' + login), login=login, provider='github',
                         customers=customers, scopes=scopes, orgs=organizations, email=profile.get('email', None),
                         email_verified=True if 'email' in profile else False)
    return jsonify(token=token.tokenize)
Exemple #42
0
def saml_response_from_idp():
    origin = request.form['RelayState']

    authn_response = saml_client().parse_authn_request_response(
        xmlstr=request.form['SAMLResponse'],
        binding=saml2.entity.BINDING_HTTP_POST
    )
    identity = authn_response.get_identity()
    subject = authn_response.get_subject()

    name = current_app.config['SAML2_USER_NAME_FORMAT'].format(**dict(map(lambda x: (x[0], x[1][0]), identity.items())))
    login = subject.text
    email = identity[current_app.config['SAML2_EMAIL_ATTRIBUTE']][0]

    # Create user if not yet there
    user = User.find_by_username(username=email)
    if not user:
        user = User(name=name, login=login, password='', email=email,
                    roles=[], text='SAML2 user', email_verified=True)
        try:
            user = user.create()
        except Exception as e:
            ApiError(str(e), 500)

    if user.status != 'active':
        raise ApiError('User {} is not active'.format(email), 403)

    groups = identity.get('groups', [])
    if not_authorized('ALLOWED_SAML2_GROUPS', groups) or not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        message = {'status': 'error', 'message': 'User {} is not authorized'.format(email)}
        return render_template('auth/saml2.html', message=message, origin=origin), 403

    user.update_last_login()

    scopes = Permission.lookup(login=user.email, roles=user.roles + groups)
    customers = get_customers(login=user.email, groups=[user.domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(), event='saml2-login', message='user login via SAML2',
                          user=user.email, customers=customers, scopes=scopes, resource_id=user.id, type='user',
                          request=request)

    token = create_token(user_id=user.id, name=user.name, login=user.email, provider='saml2', customers=customers,
                         scopes=scopes, roles=user.roles, email=user.email, email_verified=user.email_verified)

    message = {'status': 'ok', 'token': token.tokenize}
    return render_template('auth/saml2.html', message=message, origin=origin), 200
Exemple #43
0
def create_key():
    try:
        key = ApiKey.parse(request.json)
    except ValueError as e:
        raise ApiError(str(e), 400)

    if Scope.admin in g.scopes or Scope.admin_keys in g.scopes:
        key.user = key.user or g.login
    else:
        key.user = g.login

    key.customer = assign_customer(wanted=key.customer,
                                   permission=Scope.admin_keys)

    if not key.user:
        raise ApiError(
            "An API key must be associated with a 'user'. Retry with user credentials.",
            400)

    for want_scope in key.scopes:
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError(
                "Requested scope '{}' not in existing scopes: {}".format(
                    want_scope, ','.join(g.scopes)), 403)

    try:
        key = key.create()
    except Exception as e:
        raise ApiError(str(e), 500)

    write_audit_trail.send(current_app._get_current_object(),
                           event='apikey-created',
                           message='',
                           user=g.login,
                           customers=g.customers,
                           scopes=g.scopes,
                           resource_id=key.id,
                           type='apikey',
                           request=request)

    if key:
        return jsonify(status='ok', key=key.key, data=key.serialize), 201
    else:
        raise ApiError('create API key failed', 500)
Exemple #44
0
def keycloak():

    if not current_app.config['KEYCLOAK_URL']:
        raise ApiError('Must define KEYCLOAK_URL setting in server configuration.', 503)

    access_token_url = '{}/auth/realms/{}/protocol/openid-connect/token'.format(
        current_app.config['KEYCLOAK_URL'], current_app.config['KEYCLOAK_REALM'])

    payload = {
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
        'redirect_uri': request.json['redirectUri'],
        'grant_type': 'authorization_code',
        'code': request.json['code'],
    }

    try:
        r = requests.post(access_token_url, data=payload)
    except Exception:
        raise ApiError('Failed to call Keycloak API over HTTPS', 400)
    access_token = r.json()

    headers = {'Authorization': '{} {}'.format(access_token['token_type'], access_token['access_token'])}
    r = requests.get('{}/auth/realms/{}/protocol/openid-connect/userinfo'.format(
        current_app.config['KEYCLOAK_URL'], current_app.config['KEYCLOAK_REALM']), headers=headers)
    profile = r.json()

    roles = profile.get('roles', ['user'])
    login = profile['preferred_username']

    if not_authorized('ALLOWED_KEYCLOAK_ROLES', roles):
        raise ApiError('User %s is not authorized' % login, 403)

    customers = get_customers(login, groups=roles)

    auth_audit_trail.send(current_app._get_current_object(), event='keycloak-login', message='user login via Keycloak',
                          user=login, customers=customers, scopes=Permission.lookup(login, groups=roles),
                          resource_id=profile['sub'], type='keycloak', request=request)

    token = create_token(user_id=profile['sub'], name=profile['name'], login=login, provider='keycloak',
                         customers=customers, roles=roles)
    return jsonify(token=token.tokenize)
Exemple #45
0
def create_token(user_id, name, login, provider, customer, orgs=None, groups=None, roles=None, email=None, email_verified=None):
    now = datetime.utcnow()
    scopes = Permission.lookup(login, groups=(roles or []) + (groups or []) + (orgs or []))
    return Jwt(
        iss=request.url_root,
        sub=user_id,
        aud=current_app.config.get('OAUTH2_CLIENT_ID', None) or request.url_root,
        exp=(now + timedelta(days=current_app.config['TOKEN_EXPIRE_DAYS'])),
        nbf=now,
        iat=now,
        jti=str(uuid4()),
        name=name,
        preferred_username=login,
        orgs=orgs,
        roles=roles,
        groups=groups,
        provider=provider,
        scopes=scopes,
        email=email,
        email_verified=email_verified,
        customer=customer
    )
Exemple #46
0
def login():
    # lookup user from username/email
    try:
        username = request.json.get('username', None) or request.json['email']
        password = request.json['password']
    except KeyError:
        raise ApiError("must supply 'username' and 'password'", 401)

    user = User.check_credentials(username, password)
    if not user:
        raise ApiError('Invalid username or password', 401)

    # if email verification is enforced, deny login and send email
    if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
        user.send_confirmation()
        raise ApiError('email not verified', 403)

    # check allowed domain
    if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('unauthorized domain', 403)

    # check user is active & update last login
    if user.status != 'active':
        raise ApiError('User {} not active'.format(user.login), 403)
    user.update_last_login()

    groups = [g.name for g in user.get_groups()]
    scopes = Permission.lookup(login=user.login, roles=user.roles + groups)
    customers = get_customers(login=user.login, groups=[user.domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(), event='basic-auth-login', message='user login via BasicAuth',
                          user=user.login, customers=customers, scopes=scopes, resource_id=user.id, type='user',
                          request=request)

    # generate token
    token = create_token(user_id=user.id, name=user.name, login=user.login, provider='basic',
                         customers=customers, scopes=scopes, roles=user.roles, groups=groups,
                         email=user.email, email_verified=user.email_verified)
    return jsonify(token=token.tokenize)
Exemple #47
0
def update_key(key):
    if not request.json:
        raise ApiError('nothing to change', 400)

    if not current_app.config['AUTH_REQUIRED']:
        key = ApiKey.find_by_id(key)
    elif Scope.admin in g.scopes or Scope.admin_keys in g.scopes:
        key = ApiKey.find_by_id(key)
    else:
        key = ApiKey.find_by_id(key, user=g.login)

    if not key:
        raise ApiError('not found', 404)

    update = request.json
    update['customer'] = assign_customer(wanted=update.get('customer'),
                                         permission=Scope.admin_keys)

    for want_scope in update.get('scopes', []):
        if not Permission.is_in_scope(want_scope, have_scopes=g.scopes):
            raise ApiError(
                "Requested scope '{}' not in existing scopes: {}".format(
                    want_scope, ','.join(g.scopes)), 403)

    admin_audit_trail.send(current_app._get_current_object(),
                           event='apikey-updated',
                           message='',
                           user=g.login,
                           customers=g.customers,
                           scopes=g.scopes,
                           resource_id=key.id,
                           type='apikey',
                           request=request)

    updated = key.update(**request.json)
    if updated:
        return jsonify(status='ok', key=updated.serialize)
    else:
        raise ApiError('failed to update API key', 500)
Exemple #48
0
def list_perms():
    query = qb.from_params(request.args)
    perms = Permission.find_all(query)

    admin_perm = Permission(
        match='admin',
        scopes=[Scope.admin]
    )
    user_perm = Permission(
        match='user',
        scopes=current_app.config['USER_DEFAULT_SCOPES']
    )

    # add system-defined roles 'admin' and 'user'
    if 'scopes' in request.args:
        want_scopes = request.args.getlist('scopes')
        if set(admin_perm.scopes) & set(want_scopes):
            perms.append(admin_perm)
        if set(user_perm.scopes) & set(want_scopes):
            perms.append(user_perm)
    else:
        perms.append(admin_perm)
        perms.append(user_perm)

    if perms:
        return jsonify(
            status='ok',
            permissions=[perm.serialize for perm in perms],
            total=len(perms)
        )
    else:
        return jsonify(
            status='ok',
            message='not found',
            permissions=[],
            total=0
        )
Exemple #49
0
def login():
    # Retrieve required fields from client request
    try:
        email = request.json.get('username', None) or request.json['email']
        password = request.json['password']
    except KeyError:
        raise ApiError("must supply 'username' and 'password'", 401)

    username, domain = email.split('@')

    # Validate LDAP domain
    if domain not in current_app.config['LDAP_DOMAINS']:
        raise ApiError('unauthorized domain', 403)

    userdn = current_app.config['LDAP_DOMAINS'][domain] % username

    # Attempt LDAP AUTH
    try:
        trace_level = 2 if current_app.debug else 0
        ldap_connection = ldap.initialize(current_app.config['LDAP_URL'], trace_level=trace_level)
        ldap_connection.simple_bind_s(userdn, password)
    except ldap.INVALID_CREDENTIALS:
        raise ApiError('invalid username or password', 401)
    except Exception as e:
        raise ApiError(str(e), 500)

    # Create user if not yet there
    user = User.find_by_username(username=email)
    if not user:
        user = User(name=username, login=email, password='', email=email,
                    roles=[], text='LDAP user', email_verified=True)
        try:
            user = user.create()
        except Exception as e:
            ApiError(str(e), 500)

    # Assign customers & update last login time
    groups = list()
    try:
        groups_filters = current_app.config.get('LDAP_DOMAINS_GROUP', {})
        base_dns = current_app.config.get('LDAP_DOMAINS_BASEDN', {})
        if domain in groups_filters and domain in base_dns:
            resultID = ldap_connection.search(
                base_dns[domain],
                ldap.SCOPE_SUBTREE,
                groups_filters[domain].format(username=username, email=email, userdn=userdn),
                ['cn']
            )
            resultTypes, results = ldap_connection.result(resultID)
            for _dn, attributes in results:
                groups.append(attributes['cn'][0].decode('utf-8'))
    except ldap.LDAPError as e:
        raise ApiError(str(e), 500)

    # Check user is active
    if user.status != 'active':
        raise ApiError('User {} not active'.format(email), 403)
    user.update_last_login()

    scopes = Permission.lookup(login=user.email, roles=user.roles)
    customers = get_customers(login=user.email, groups=[user.domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(), event='basic-ldap-login', message='user login via LDAP',
                          user=user.email, customers=customers, scopes=scopes, resource_id=user.id, type='user',
                          request=request)

    # Generate token
    token = create_token(user_id=user.id, name=user.name, login=user.email, provider='ldap', customers=customers,
                         scopes=scopes, roles=user.roles, email=user.email, email_verified=user.email_verified)
    return jsonify(token=token.tokenize)
Exemple #50
0
def openid():

    oidc_configuration, jwt_key_set = get_oidc_configuration(current_app)
    token_endpoint = oidc_configuration['token_endpoint']
    userinfo_endpoint = oidc_configuration['userinfo_endpoint']

    data = {
        'grant_type': 'authorization_code',
        'code': request.json['code'],
        'redirect_uri': request.json['redirectUri'],
        'client_id': request.json['clientId'],
        'client_secret': current_app.config['OAUTH2_CLIENT_SECRET'],
    }
    r = requests.post(token_endpoint, data)
    token = r.json()

    try:
        if current_app.config['OIDC_VERIFY_TOKEN']:
            jwt_header = jwt.get_unverified_header(token['id_token'])
            public_key = jwt_key_set[jwt_header['kid']]

            id_token = jwt.decode(
                token['id_token'],
                key=public_key,
                algorithms=jwt_header['alg']
            )
        else:
            id_token = jwt.decode(
                token['id_token'],
                verify=False
            )
    except Exception:
        current_app.logger.warning('No ID token in OpenID Connect token response.')
        id_token = {}

    try:
        headers = {'Authorization': '{} {}'.format(token.get('token_type', 'Bearer'), token['access_token'])}
        r = requests.get(userinfo_endpoint, headers=headers)
        userinfo = r.json()
    except Exception:
        raise ApiError('No access token in OpenID Connect token response.')

    subject = userinfo['sub']
    name = userinfo.get('name') or id_token.get('name')
    nickname = userinfo.get('nickname')
    email = userinfo.get('email') or id_token.get('email')
    email_verified = userinfo.get('email_verified', id_token.get('email_verified', bool(email)))
    picture = userinfo.get('picture') or id_token.get('picture')

    role_claim = current_app.config['OIDC_ROLE_CLAIM']
    group_claim = current_app.config['OIDC_GROUP_CLAIM']
    custom_claims = {
        role_claim: userinfo.get(role_claim) or id_token.get(role_claim, []),
        group_claim: userinfo.get(group_claim) or id_token.get(group_claim, []),
    }

    login = userinfo.get('preferred_username', nickname or email)
    if not login:
        raise ApiError("Must support one of the following OpenID claims: 'preferred_username', 'nickname' or 'email'", 400)

    user = User.find_by_id(id=subject)
    if not user:
        user = User(id=subject, name=name, login=login, password='', email=email,
                    roles=[], text='', email_verified=email_verified)
        user.create()
    else:
        user.update(login=login, email=email)

    roles = custom_claims[role_claim] or user.roles
    groups = custom_claims[group_claim]

    if user.status != 'active':
        raise ApiError('User {} is not active'.format(login), 403)

    if not_authorized('ALLOWED_OIDC_ROLES', roles) and not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
        raise ApiError('User {} is not authorized'.format(login), 403)
    user.update_last_login()

    scopes = Permission.lookup(login, roles)
    customers = get_customers(login, groups=[user.domain] + groups)

    auth_audit_trail.send(current_app._get_current_object(), event='openid-login', message='user login via OpenID Connect',
                          user=login, customers=customers, scopes=scopes, resource_id=subject, type='user', request=request)

    token = create_token(user_id=subject, name=name, login=login, provider='openid', customers=customers,
                         scopes=scopes, email=email, email_verified=email_verified, picture=picture, **custom_claims)
    return jsonify(token=token.tokenize)
Exemple #51
0
        def wrapped(*args, **kwargs):

            # API Key (Authorization: Key <key>)
            if 'Authorization' in request.headers:
                auth_header = request.headers['Authorization']
                m = re.match(r'Key (\S+)', auth_header)
                key = m.group(1) if m else None
            # API Key (X-API-Key: <key>)
            elif 'X-API-Key' in request.headers:
                key = request.headers['X-API-Key']
            # API Key (/foo?api-key=<key>)
            else:
                key = request.args.get('api-key', None)

            if key:
                key_info = ApiKey.verify_key(key)
                if not key_info:
                    raise ApiError("API key parameter '%s' is invalid" % key, 401)
                g.user_id = None
                g.login = key_info.user
                g.customers = [key_info.customer] if key_info.customer else []
                g.scopes = key_info.scopes  # type: List[Scope]

                if not Permission.is_in_scope(scope, have_scopes=g.scopes):
                    raise ApiError('Missing required scope: %s' % scope.value, 403)
                else:
                    return f(*args, **kwargs)

            # Bearer Token
            auth_header = request.headers.get('Authorization', '')
            m = re.match(r'Bearer (\S+)', auth_header)
            token = m.group(1) if m else None

            if token:
                try:
                    jwt = Jwt.parse(token)
                except DecodeError:
                    raise ApiError('Token is invalid', 401)
                except ExpiredSignature:
                    raise ApiError('Token has expired', 401)
                except InvalidAudience:
                    raise ApiError('Invalid audience', 401)
                g.user_id = jwt.subject
                g.login = jwt.preferred_username
                g.customers = jwt.customers
                g.scopes = jwt.scopes  # type: List[Scope]

                if not Permission.is_in_scope(scope, have_scopes=g.scopes):
                    raise ApiError('Missing required scope: %s' % scope.value, 403)
                else:
                    return f(*args, **kwargs)

            # Basic Auth
            auth_header = request.headers.get('Authorization', '')
            m = re.match(r'Basic (\S+)', auth_header)
            credentials = m.group(1) if m else None

            if credentials:
                try:
                    username, password = base64.b64decode(credentials).decode('utf-8').split(':')
                except Exception as e:
                    raise BasicAuthError('Invalid credentials', 400, errors=[str(e)])

                user = User.check_credentials(username, password)
                if not user:
                    raise BasicAuthError('Authorization required', 401)

                if current_app.config['EMAIL_VERIFICATION'] and not user.email_verified:
                    raise BasicAuthError('email not verified', 401)

                if not_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]):
                    raise BasicAuthError('Unauthorized domain', 403)

                g.user_id = user.id
                g.login = user.email
                g.customers = get_customers(user.email, groups=[user.domain])
                g.scopes = Permission.lookup(user.email, roles=user.roles)  # type: List[Scope]

                if not Permission.is_in_scope(scope, have_scopes=g.scopes):
                    raise BasicAuthError('Missing required scope: %s' % scope.value, 403)
                else:
                    return f(*args, **kwargs)

            if not current_app.config['AUTH_REQUIRED']:
                g.user_id = None
                g.login = None
                g.customers = []
                g.scopes = []  # type: List[Scope]
                return f(*args, **kwargs)

            # Google App Engine Cron Service
            if request.headers.get('X-Appengine-Cron', False) and request.headers.get('X-Forwarded-For', '') == '0.1.0.1':
                return f(*args, **kwargs)

            raise ApiError('Missing authorization API Key or Bearer Token', 401)