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' params = { 'client_id': request.json['clientId'], 'redirect_uri': request.json['redirectUri'], 'client_secret': current_app.config['OAUTH2_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 is_authorized('ALLOWED_GITHUB_ORGS', organizations): raise ApiError("User %s is not authorized" % login, 403) customer = get_customer(login, organizations) token = create_token(profile['id'], profile.get('name', '@'+login), login, provider='github', customer=customer, orgs=organizations, email=profile.get('email', None), email_verified=True if 'email' in profile else False) return jsonify(token=token.tokenize)
def gitlab(): access_token_url = current_app.config['GITLAB_URL'] + '/oauth/token' gitlab_api_url = current_app.config['GITLAB_URL'] + '/api/v3' 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: return jsonify(status="error", message="Failed to call Gitlab API over HTTPS") access_token = r.json() r = requests.get(gitlab_api_url+'/user', params=access_token) profile = r.json() r = requests.get(gitlab_api_url+'/groups', params=access_token) groups = [g['path'] for g in r.json()] login = profile['username'] if is_authorized('ALLOWED_GITLAB_GROUPS', groups): raise ApiError("User %s is not authorized" % login, 403) customer = get_customer(login, groups) token = create_token(profile['id'], profile.get('name', '@'+login), login, provider='gitlab', customer=customer, groups=groups, email=profile.get('email', None), email_verified=True if profile.get('email', None) else False) return jsonify(token=token.tokenize)
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)
def keycloak(): if not current_app.config['KEYCLOAK_URL']: return jsonify(status="error", message="Must define KEYCLOAK_URL setting in server configuration."), 503 access_token_url = "{0}/auth/realms/{1}/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: return jsonify(status="error", message="Failed to call Keycloak API over HTTPS") access_token = r.json() headers = {"Authorization": "{0} {1}".format(access_token['token_type'], access_token['access_token'])} r = requests.get("{0}/auth/realms/{1}/protocol/openid-connect/userinfo".format(current_app.config['KEYCLOAK_URL'], current_app.config['KEYCLOAK_REALM']), headers=headers) profile = r.json() roles = profile['roles'] login = profile['preferred_username'] if is_authorized('ALLOWED_KEYCLOAK_ROLES', roles): raise ApiError("User %s is not authorized" % login, 403) customer = get_customer(login, groups=roles) token = create_token(profile['sub'], profile['name'], login, provider='keycloak', customer=customer, roles=roles) return jsonify(token=token.tokenize)
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)
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)
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)
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)
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.find_by_email(email=username) if not user: raise ApiError("invalid username or password", 401) if not user.verify_password(password): 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: hash = str(uuid4()) send_confirmation(user, hash) user.set_email_hash(hash) raise ApiError('email not verified', 401) # check user is active if user.status != 'active': raise ApiError('user not active', 403) # check allowed domain if is_authorized('ALLOWED_EMAIL_DOMAINS', groups=[user.domain]): raise ApiError("unauthorized domain", 403) # assign customer & update last login time customer = get_customer(user.email, groups=[user.domain]) user.update_last_login() # generate token token = create_token(user.id, user.name, user.email, provider='basic', customer=customer, roles=user.roles, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def signup(): try: user = User.parse(request.json) except Exception as e: raise ApiError(str(e), 400) # check allowed domain if is_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: hash = str(uuid4()) send_confirmation(user, hash) user.set_email_hash(hash) raise ApiError('email not verified', 401) # check user is active if user.status != 'active': raise ApiError('user not active', 403) # assign customer & update last login time customer = get_customer(user.email, groups=[user.domain]) user.update_last_login() # generate token token = create_token(user.id, user.name, user.email, provider='basic', customer=customer, roles=user.roles, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
def keycloak(): if not current_app.config['KEYCLOAK_URL']: return jsonify( status='error', message='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: return jsonify(status='error', message='Failed to call Keycloak API over HTTPS') 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['roles'] 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)
def login(): try: login = request.json.get('username') or request.json['email'] password = request.json['password'] except KeyError: raise ApiError("must supply 'username' and 'password'", 401) if not password: raise ApiError('password not allowed to be empty', 401) try: if '\\' in login: domain, username = login.split('\\') else: username, domain = login.split('@') except ValueError: if current_app.config['LDAP_DEFAULT_DOMAIN']: username = login domain = current_app.config['LDAP_DEFAULT_DOMAIN'] else: raise ApiError('expected username with domain', 401) # Validate LDAP domain if (domain not in current_app.config['ALLOWED_EMAIL_DOMAINS'] and domain not in current_app.config['LDAP_DOMAINS']): raise ApiError('unauthorized domain', 403) # LDAP certificate settings if current_app.config['LDAP_CACERT']: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_HARD) ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, current_app.config['LDAP_CACERT']) # Allow LDAP server to use a self-signed certificate if current_app.config['LDAP_ALLOW_SELF_SIGNED_CERT']: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW) # Set LDAP Timeout: if current_app.config['LDAP_QUERY_TIMEOUT_SECONDS']: ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, current_app.config['LDAP_QUERY_TIMEOUT_SECONDS']) # Initialise ldap connection try: trace_level = 2 if current_app.debug else 0 # XXX - do not set in production environments ldap_connection = ldap.initialize(current_app.config['LDAP_URL'], trace_level=trace_level) except Exception as e: raise ApiError(str(e), 500) # bind user credentials ldap_bind_username = current_app.config['LDAP_BIND_USERNAME'] ldap_bind_password = current_app.config['LDAP_BIND_PASSWORD'] if ldap_bind_username: try: ldap_connection.simple_bind_s(ldap_bind_username, ldap_bind_password) except ldap.INVALID_CREDENTIALS: raise ApiError('invalid ldap bind credentials', 500) # Set default base DN for user and group search base_dn = current_app.config['LDAP_BASEDN'] # If user search filter exist # Search the user using the provided User Search filter for the current domain # If one user is found # Set the DN as the one found # Set email retreived from AD # If more than one user is found # Except: Search query is bad defined # Else # Set the DN as the one found in LDAP_DOMAINS variable user_filter = current_app.config['LDAP_USER_FILTER'] user_base_dn = current_app.config['LDAP_USER_BASEDN'] user_attrs = [ current_app.config['LDAP_USER_NAME_ATTR'], current_app.config['LDAP_USER_EMAIL_ATTR'] ] if user_filter: result = [ r for r in ldap_connection.search_s(base=user_base_dn or base_dn, scope=ldap.SCOPE_SUBTREE, filterstr=user_filter.format( username=username), attrlist=user_attrs) if None not in r ] if len(result) > 1: raise ApiError( 'invalid search query for domain "{}"'.format(domain), 500) elif len(result) == 0: raise ApiError('invalid username or password', 401) user_dn = result[0][0] name = result[0][1][ current_app.config['LDAP_USER_NAME_ATTR']][0].decode( 'utf-8', 'ignore') email = result[0][1][ current_app.config['LDAP_USER_EMAIL_ATTR']][0].decode( 'utf-8', 'ignore') email_verified = bool(email) else: if '%' in current_app.config['LDAP_DOMAINS'][domain]: user_dn = current_app.config['LDAP_DOMAINS'][domain] % username else: user_dn = current_app.config['LDAP_DOMAINS'][domain].format( username) name = username email = '{}@{}'.format(username, domain) email_verified = False # Authenticate user logging in try: ldap_connection.simple_bind_s(user_dn, password) except ldap.INVALID_CREDENTIALS: raise ApiError('invalid username or password', 401) login = email or username user = User.find_by_username(username=login) if not user: user = User(name=name, login=login, password='', email=email, roles=current_app.config['USER_ROLES'], text='LDAP user', email_verified=email_verified) user = user.create() else: user.update(login=login, email=email, email_verified=email_verified) if ldap_bind_username: try: ldap_connection.simple_bind_s(ldap_bind_username, ldap_bind_password) except ldap.INVALID_CREDENTIALS: raise ApiError('invalid ldap bind credentials', 500) # Assign customers & update last login time group_filter = current_app.config['LDAP_GROUP_FILTER'] group_base_dn = current_app.config['LDAP_GROUP_BASEDN'] groups = list() if group_filter: result = ldap_connection.search_s( base=group_base_dn or base_dn, scope=ldap.SCOPE_SUBTREE, filterstr=group_filter.format(username=username, email=email, userdn=user_dn), attrlist=[current_app.config['LDAP_GROUP_NAME_ATTR']]) for group_dn, group_attrs in result: if current_app.config['LDAP_GROUP_NAME_ATTR'] in group_attrs.keys( ): groups.extend([ g.decode('utf-8', 'ignore') for g in group_attrs[ current_app.config['LDAP_GROUP_NAME_ATTR']] ]) else: groups.append(group_dn) # Check user is active if user.status != 'active': raise ApiError('User {} not active'.format(login), 403) if not_authorized('ALLOWED_LDAP_GROUPS', groups): raise ApiError('User {} is not authorized'.format(login), 403) user.update_last_login() scopes = Permission.lookup(login=login, roles=user.roles + groups) customers = get_customers(login=login, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='basic-ldap-login', message='user login via LDAP', 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.email, provider='ldap', customers=customers, scopes=scopes, roles=user.roles, groups=groups, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
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() if 'error' in token: error_text = token.get('error_description') or token['error'] raise ApiError(error_text) 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') username = userinfo.get('preferred_username') or id_token.get( 'preferred_username') nickname = userinfo.get('nickname') or id_token.get('nickname') email = userinfo.get('email') or id_token.get('email') email_verified = userinfo.get('email_verified', id_token.get('email_verified', bool(email))) email_verified = True if email_verified == 'true' else email_verified # Cognito returns string boolean 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 = username or 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=current_app.config['AUTH_PROVIDER'], customers=customers, scopes=scopes, email=email, email_verified=email_verified, picture=picture, **custom_claims) return jsonify(token=token.tokenize)
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' 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'], } r = requests.post(access_token_url, data) 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)
def login(): # Allow LDAP server to use a self signed certificate if current_app.config['LDAP_ALLOW_SELF_SIGNED_CERT']: ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW) # Retrieve required fields from client request try: login = request.json.get('username', None) or request.json['email'] password = request.json['password'] except KeyError: raise ApiError("must supply 'username' and 'password'", 401) try: if '\\' in login: domain, username = login.split('\\') email = '' email_verified = False else: username, domain = login.split('@') email = login email_verified = True except ValueError: raise ApiError('expected username with domain', 401) # 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) # Get email address from LDAP if not email_verified: try: ldap_result = ldap_connection.search_s(userdn, ldap.SCOPE_SUBTREE, '(objectClass=*)', ['mail']) email = ldap_result[0][1]['mail'][0].decode(sys.stdout.encoding) email_verified = True except Exception: email = '{}@{}'.format(username, domain) # Create user if not yet there user = User.find_by_username(username=login) if not user: user = User(name=username, login=login, password='', email=email, roles=[], text='LDAP user', email_verified=email_verified) 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(login), 403) user.update_last_login() scopes = Permission.lookup(login=login, roles=user.roles + groups) customers = get_customers(login=login, groups=[user.domain] + groups) auth_audit_trail.send(current_app._get_current_object(), event='basic-ldap-login', message='user login via LDAP', 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.email, provider='ldap', customers=customers, scopes=scopes, roles=user.roles, groups=groups, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
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)
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 = identity[current_app.config['SAML2_LOGIN_ATTRIBUTE']][0] 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=current_app.config['USER_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(f'User {email} is not active', 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': f'User {email} is not authorized' } 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, roles=user.roles, groups=groups, 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, groups=groups, 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
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) # Define ldap filter use %s for username ldapfilter = f'(mail={email})' # Attempt LDAP AUTH with binddn try: ldap_connection = ldap.initialize(ldapurl) ldap_connection.simple_bind_s(binddn, binddnpw) except ldap.INVALID_CREDENTIALS: raise ApiError("invalid username or password for binddn", 401) except Exception as e: raise ApiError(str(e), 500) # Start LDAP search try: ldapquery = ldap_connection.search_s(ldapbasedn, ldap.SCOPE_SUBTREE, ldapfilter, ['cn']) userdn = ldapquery[0][0] usercn = str(b''.join(ldapquery[0][1]['cn']), 'utf-8') except Exception: raise ApiError("invalid username or basedn", 401) # Attempt LDAP AUTH try: ldap_connection.simple_bind_s(userdn, password) except ldap.INVALID_CREDENTIALS: raise ApiError("invalid password", 401) except Exception as e: raise ApiError(str(e), 500) # Create user if not yet there user = User.find_by_email(email=email) if not user: user = User(usercn, email, "", ldaprole.split(), "LDAP user", email_verified=True) user.create() # Check user is active if user.status != 'active': raise ApiError('user not active', 403) # Assign customers & update last login time customers = get_customers(user.email, groups=[user.domain]) user.update_last_login() # Generate token token = create_token(user.id, user.name, user.email, provider='basic_ldap', customers=customers, roles=user.roles, email=user.email, email_verified=user.email_verified) return jsonify(token=token.tokenize)
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)
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) data = { 'grant_type': 'authorization_code', 'code': request.json['code'], 'redirect_uri': request.json['redirectUri'], 'client_id': request.json['clientId'], 'client_secret': client_secret, } r = requests.post(access_token_url, data, headers={'Accept': 'application/json'}) token = r.json() headers = {'Authorization': 'token {}'.format(token['access_token'])} r = requests.get(github_api_url + '/user', headers=headers) profile = r.json() r = requests.get(github_api_url + '/user/orgs', headers=headers) # list public and private Github orgs organizations = [o['login'] for o in r.json()] subject = str(profile['id']) name = profile['name'] username = '******' + profile['login'] email = profile['email'] email_verified = True if email else False picture = profile['avatar_url'] login = username or email if not login: raise ApiError("Must allow access to GitHub user profile information: 'login' 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 = organizations or user.roles groups = organizations if user.status != 'active': raise ApiError('User {} is not active'.format(login), 403) if not_authorized('ALLOWED_GITHUB_ORGS', organizations): 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='github-login', message='user login via GitHub', user=login, customers=customers, scopes=scopes, resource_id=subject, type='user', request=request) token = create_token(user_id=subject, name=name, login=login, provider='github', customers=customers, scopes=scopes, orgs=organizations, email=email, email_verified=email_verified, picture=picture) return jsonify(token=token.tokenize)
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 + groups) 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)
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)