def get_signed_url( self, protocol, action, expires_in, force_signed_url=True, r_pays_project=None, file_name=None, ): if self.index_document.get("authz"): action_to_permission = { "upload": "write-storage", "download": "read-storage", } if not self.check_authz(action_to_permission[action]): raise Unauthorized( f"Either you weren't logged in or you don't have " f"{action_to_permission[action]} permission " f"on {self.index_document['authz']} for fence") else: if self.public_acl and action == "upload": raise Unauthorized( "Cannot upload on public files while using acl field") # don't check the authorization if the file is public # (downloading public files with no auth is fine) if not self.public_acl and not self.check_authorization(action): raise Unauthorized( f"You don't have access permission on this file: {self.file_id}" ) if action is not None and action not in SUPPORTED_ACTIONS: raise NotSupported("action {} is not supported".format(action)) return self._get_signed_url(protocol, action, expires_in, force_signed_url, r_pays_project, file_name)
def get_signed_url(self, protocol, action, expires_in, force_signed_url=True): if self.public and action == "upload": raise Unauthorized("Cannot upload on public files") # don't check the authorization if the file is public # (downloading public files with no auth is fine) if not self.public and not self.check_authorization(action): raise Unauthorized("You don't have access permission on this file") if action is not None and action not in SUPPORTED_ACTIONS: raise NotSupported("action {} is not supported".format(action)) return self._get_signed_url(protocol, action, expires_in, force_signed_url)
def any_access(): """ Check if the user is in our database :note if a user is specified with empty access it still counts :query project: (optional) Check for read access to a specific program/project """ project = flask.request.args.get("project") projects = None if flask.g.token is None: flask.g.user = current_session.merge(flask.g.user) projects = flask.g.user.project_access else: projects = flask.g.token["context"]["user"]["projects"] success = False if not project and len(projects) > 0: success = True elif project and project in projects: access = projects[project] if "read" in access: success = True if success: resp = flask.make_response(flask.jsonify({"result": "success"}), 200) resp.headers["REMOTE_USER"] = flask.g.user.username return resp raise Unauthorized("Please login")
def create_user_access_token(keypair, api_key, expires_in): """ create access token given a user's api key Args: keypair: RSA keypair for signing jwt api_key: user created jwt token, the azp should match with user.id expires_in: expiration time in seconds Return: access token """ try: claims = validate_jwt(api_key, scope={"fence"}, purpose="api_key") # scopes = claims["scope"] ##### begin api key patch block ##### # TODO: In the next release, remove this block and uncomment line above. # Old API keys are not compatible with new validation # This is to help transition try: scopes = claims["scope"] except KeyError as e: scopes = claims["aud"] ##### end api key patch block ##### user = get_user_from_claims(claims) except Exception as e: raise Unauthorized(str(e)) return token.generate_signed_access_token( keypair.kid, keypair.private_key, user, expires_in, scopes ).token
def get_jwt(): """ Return the user's JWT from authorization header. Requires flask application context. Raises: - Unauthorized, if header is missing or not in the correct format """ header = flask.request.headers.get("Authorization") if not header: raise Unauthorized("missing authorization header") try: bearer, token = header.split(" ") except ValueError: raise Unauthorized("authorization header not in expected format") if bearer.lower() != "bearer": raise Unauthorized("expected bearer token in auth header") return token
def handle_login(scope): if flask.session.get('username'): login_user( flask.request, flask.session['username'], flask.session['provider'], ) eppn = flask.request.headers.get( flask.current_app.config['SHIBBOLETH_HEADER'] ) if flask.current_app.config.get('MOCK_AUTH') is True: eppn = 'test' # if there is authorization header for oauth if 'Authorization' in flask.request.headers: has_oauth(scope=scope) # if there is shibboleth session, then create user session and # log user in elif eppn: username = eppn.split('!')[-1] flask.session['username'] = username flask.session['provider'] = IdentityProvider.itrust login_user(flask.request, username, flask.session['provider']) else: raise Unauthorized("Please login")
def get_credential_to_access_bucket(self, aws_creds, expires_in): s3_buckets = get_value( flask.current_app.config, "S3_BUCKETS", InternalError("buckets not configured"), ) if len(aws_creds) == 0 and len(s3_buckets) == 0: raise InternalError("no bucket is configured") if len(aws_creds) == 0 and len(s3_buckets) > 0: raise InternalError("credential for buckets is not configured") bucket_cred = None for pattern in s3_buckets: if re.match("^" + pattern + "$", self.parsed_url.netloc): bucket_cred = s3_buckets[pattern] break if bucket_cred is None: raise Unauthorized("permission denied for bucket") cred_key = get_value( bucket_cred, "cred", InternalError("credential of that bucket is missing")) if cred_key == "*": return {"aws_access_key_id": "*"} if "role-arn" not in bucket_cred: return get_value( aws_creds, cred_key, InternalError("aws credential of that bucket is not found"), ) else: return S3IndexedFileLocation.assume_role(aws_creds, bucket_cred, cred_key, expires_in)
def get(self): """ Complete the shibboleth login. """ shib_header = config.get("SHIBBOLETH_HEADER") if not shib_header: raise InternalError("Missing shibboleth header configuration") # eppn stands for eduPersonPrincipalName username = flask.request.headers.get("eppn") entityID = flask.session.get("entityID") # if eppn not available or logging in through NIH if not username or not entityID or entityID == "urn:mace:incommon:nih.gov": persistent_id = flask.request.headers.get(shib_header) username = persistent_id.split("!")[-1] if persistent_id else None if not username: # some inCommon providers are not returning eppn # or persistent_id. See PXP-4309 # print("shib_header", shib_header) # print("flask.request.headers", flask.request.headers) raise Unauthorized("Unable to retrieve username") idp = IdentityProvider.itrust if entityID: idp = entityID login_user(flask.request, username, idp) if flask.session.get("redirect"): return flask.redirect(flask.session.get("redirect")) return "logged in"
def get(self): """Handle ``GET /login/fence/login``.""" # Check that the state passed back from IDP fence is the same as the # one stored previously. mismatched_state = ( 'state' not in flask.request.args or 'state' not in flask.session or flask.request.args['state'] != flask.session.pop('state')) if mismatched_state: raise Unauthorized('authorization request failed; state mismatch') # Get the token response and log in the user. redirect_uri = flask.current_app.fence_client.session.redirect_uri tokens = flask.current_app.fence_client.fetch_access_token( redirect_uri, **flask.request.args.to_dict()) id_token_claims = validate_jwt(tokens['id_token'], aud={'openid'}, purpose='id', attempt_refresh=True) username = id_token_claims['context']['user']['name'] flask.session['username'] = username flask.session['provider'] = IdentityProvider.fence login_user(flask.request, username, IdentityProvider.fence) if 'redirect' in flask.session: return flask.redirect(flask.session.get('redirect')) return flask.jsonify({'username': username})
def set_acls(self): if "acl" in self.index_document: return set(self.index_document["acl"]) elif "acls" in self.metadata: return set(self.metadata["acls"].split(",")) else: raise Unauthorized("This file is not accessible")
def has_oauth(scope=None): scope = scope or set() scope.update({"openid"}) try: access_token_claims = validate_jwt(aud=scope, purpose="access") except JWTError as e: raise Unauthorized("failed to validate token: {}".format(e)) user_id = access_token_claims["sub"] user = current_session.query(User).filter_by(id=int(user_id)).first() if not user: raise Unauthorized("no user found with id: {}".format(user_id)) # set some application context for current user and client id flask.g.user = user # client_id should be None if the field doesn't exist or is empty flask.g.client_id = access_token_claims.get("azp") or None flask.g.token = access_token_claims
def check_scope_and_call(*args, **kwargs): if "_all" in flask.g.scopes or scope in flask.g.scopes: return f(*args, **kwargs) else: raise Unauthorized( "Requested scope {} can't access this endpoint".format( scope))
def get_current_user(): username = flask.session.get('username') if flask.current_app.config.get('MOCK_AUTH', False) is True: username = '******' if not username: raise Unauthorized("User not logged in") return current_session.query(User).filter_by(username=username).first()
def _get_patched_service_account_error_status(id_, sa): """ Get error status for attempting to patch given service account with access. Args: id_ (str): Google service account identifier to update sa ( fence.resources.google.service_account.GoogleServiceAccountRegistration ): the service account object with its email, project_access, a google project, and optionally a user who is attempting to modify/add """ # check if user has permission to update the service account authorized = is_user_member_of_all_google_projects( sa.user_id, [sa.google_project_id] ) if not authorized: msg = ( 'User "{}" does not have permission to update the provided ' 'service account "{}".'.format(sa.user_id, id_) ) raise Unauthorized(msg) error_response = _get_service_account_error_status(sa) return error_response
def get(self): """Handle ``GET /login/fence/login``.""" # Check that the state passed back from IDP fence is the same as the # one stored previously. mismatched_state = ( "state" not in flask.request.args or "state" not in flask.session or flask.request.args["state"] != flask.session.pop("state", "")) if mismatched_state and not config.get("MOCK_AUTH"): raise Unauthorized( "Login flow was interrupted (state mismatch). Please go back to the" " login page for the original application to continue.") # Get the token response and log in the user. redirect_uri = flask.current_app.fence_client._get_session( ).redirect_uri tokens = flask.current_app.fence_client.fetch_access_token( redirect_uri, **flask.request.args.to_dict()) id_token_claims = validate_jwt(tokens["id_token"], aud={"openid"}, purpose="id", attempt_refresh=True) username = id_token_claims["context"]["user"]["name"] login_user( username, IdentityProvider.fence, fence_idp=flask.session.get("fence_idp"), shib_idp=flask.session.get("shib_idp"), ) self.post_login() if "redirect" in flask.session: return flask.redirect(flask.session.get("redirect")) return flask.jsonify({"username": username})
def wrapper(*args, **kwargs): if flask.session.get("username"): login_user(flask.session["username"], flask.session["provider"]) return f(*args, **kwargs) eppn = None if config["LOGIN_OPTIONS"]: enable_shib = "shibboleth" in [ option["idp"] for option in config["LOGIN_OPTIONS"] ] else: # fall back on "providers" enable_shib = "shibboleth" in config.get( "ENABLED_IDENTITY_PROVIDERS", {}).get("providers", {}) if enable_shib and "SHIBBOLETH_HEADER" in config: eppn = flask.request.headers.get(config["SHIBBOLETH_HEADER"]) if config.get("MOCK_AUTH") is True: eppn = "test" # if there is authorization header for oauth if "Authorization" in flask.request.headers: has_oauth(scope=scope) return f(*args, **kwargs) # if there is shibboleth session, then create user session and # log user in elif eppn: username = eppn.split("!")[-1] flask.session["username"] = username flask.session["provider"] = IdentityProvider.itrust login_user(username, flask.session["provider"]) return f(*args, **kwargs) else: raise Unauthorized("Please login")
def wrapper(*args, **kwargs): if flask.session.get('username'): login_user( flask.request, flask.session['username'], flask.session['provider'], ) return f(*args, **kwargs) eppn = None enable_shib = ( 'shibboleth' in flask.current_app.config.get('ENABLED_IDENTITY_PROVIDERS', []) ) if enable_shib and 'SHIBBOLETH_HEADER' in flask.current_app.config: eppn = flask.request.headers.get( flask.current_app.config['SHIBBOLETH_HEADER'] ) if flask.current_app.config.get('MOCK_AUTH') is True: eppn = 'test' # if there is authorization header for oauth if 'Authorization' in flask.request.headers: has_oauth(scope=scope) return f(*args, **kwargs) # if there is shibboleth session, then create user session and # log user in elif eppn: username = eppn.split('!')[-1] flask.session['username'] = username flask.session['provider'] = IdentityProvider.itrust login_user(flask.request, username, flask.session['provider']) return f(*args, **kwargs) else: raise Unauthorized("Please login")
def force_update_google_link(DB, username, google_email): """ WARNING: This function circumvents Google Auth flow, and should only be used for internal testing! WARNING: This function assumes that a user already has a proxy group! Adds user's google account to proxy group and/or updates expiration for that google account's access. WARNING: This assumes that provided arguments represent valid information. This BLINDLY adds without verification. Do verification before this. Specifically, this ASSUMES that the proxy group provided belongs to the given user and that the user has ALREADY authenticated to prove the provided google_email is also their's. Args: DB username (str): Username to link with google_email (str): Google email to link to Raises: NotFound: Linked Google account not found Unauthorized: Couldn't determine user Returns: Expiration time of the newly updated google account's access """ import fence.settings cirrus_config.update(**fence.settings.CIRRUS_CFG) db = SQLAlchemyDriver(DB) with db.session as session: user_account = session.query(User).filter( User.username == username).first() if user_account: user_id = user_account.id proxy_group_id = user_account.google_proxy_group_id else: raise Unauthorized("Could not determine authed user " "from session. Unable to link Google account.") user_google_account = (session.query(UserGoogleAccount).filter( UserGoogleAccount.email == google_email).first()) if not user_google_account: user_google_account = add_new_user_google_account( user_id, google_email, session) now = int(time.time()) expiration = now + GOOGLE_ACCOUNT_ACCESS_EXPIRES_IN force_update_user_google_account_expiration(user_google_account, proxy_group_id, google_email, expiration, session) session.commit() return expiration
def get_current_user(flask_session=None): flask_session = flask_session or flask.session username = flask_session.get("username") if config.get("MOCK_AUTH", False) is True: username = "******" if not username: raise Unauthorized("User not logged in") return query_for_user(session=current_session, username=username)
def get_signed_url(self, protocol, action, expires_in): if not self.public and not self.check_authorization(action): raise Unauthorized("You don't have access permission on this file") if action is not None and action not in SUPPORTED_ACTIONS: raise NotSupported("action {} is not supported".format(action)) return self._get_signed_url(protocol, action, expires_in)
def get_current_user(flask_session=None): flask_session = flask_session or flask.session username = flask_session.get("username") if flask.current_app.config.get("MOCK_AUTH", False) is True: username = "******" if not username: raise Unauthorized("User not logged in") return (current_session.query(User).filter( func.lower(User.username) == username.lower()).first())
def get_jwt_header(): """ Get the user's JWT from the Authorization header, or raise Unauthorized on failure. Return just the entire JWT as a string, without further validation or processing. """ try: header = flask.request.headers["Authorization"] except KeyError: raise Unauthorized("missing authorization header") if not header.lower().startswith("bearer"): raise Unauthorized( "unexpected Authorization header format (expected `Bearer`") try: jwt = header.split(" ")[1] except IndexError: raise Unauthorized("authorization header missing token") return jwt
def get(self): """Handle ``GET /login/fence/login``.""" # Check that the state passed back from IDP fence is the same as the # one stored previously. mismatched_state = ( "state" not in flask.request.args or "state" not in flask.session or flask.request.args["state"] != flask.session.pop("state", "")) if mismatched_state and not config.get("MOCK_AUTH"): raise Unauthorized( "Login flow was interrupted (state mismatch). Please go back to the" " login page for the original application to continue.") # Get the token response and log in the user. redirect_uri = flask.current_app.fence_client._get_session( ).redirect_uri tokens = flask.current_app.fence_client.fetch_access_token( redirect_uri, **flask.request.args.to_dict()) try: # For multi-Fence setup with two Fences >=5.0.0 id_token_claims = validate_jwt( tokens["id_token"], aud=self.client.client_id, scope={"openid"}, purpose="id", attempt_refresh=True, ) except JWTError: # Since fenceshib cannot be updated to issue "new-style" ID tokens # (where scopes are in the scope claim and aud is in the aud claim), # allow also "old-style" Fence ID tokens. id_token_claims = validate_jwt( tokens["id_token"], aud="openid", scope=None, purpose="id", attempt_refresh=True, ) username = id_token_claims["context"]["user"]["name"] email = id_token_claims["context"]["user"].get("email") login_user( username, IdentityProvider.fence, fence_idp=flask.session.get("fence_idp"), shib_idp=flask.session.get("shib_idp"), email=email, ) self.post_login() if config["REGISTER_USERS_ON"]: if not flask.g.user.additional_info.get("registration_info"): return flask.redirect(config["BASE_URL"] + flask.url_for("register.register_user")) if "redirect" in flask.session: return flask.redirect(flask.session.get("redirect")) return flask.jsonify({"username": username})
def logout(next_url=None): # Call get_current_user (but ignore the result) just to check that either # the user is logged in or that authorization is mocked. user = get_current_user() if not user: raise Unauthorized("You are not logged in") if flask.session['provider'] == IdentityProvider.itrust: next_url = flask.current_app.config['ITRUST_GLOBAL_LOGOUT'] + next_url flask.session.clear() return next_url
def _is_valid_service_account(sa_email, google_project_id): """ Validate the given registered service account and remove if invalid. Args: sa_email(str): service account email google_project_id(str): google project id """ with GoogleCloudManager(google_project_id) as gcm: google_project_number = get_google_project_number(google_project_id, gcm) has_access = bool(google_project_number) if not has_access: # if our monitor doesn't have access at this point, just don't return any # information. When the project check runs, it will catch the monitor missing # error and add it to the removal reasons raise Unauthorized( "Google Monitoring SA doesn't have access to Google Project: {}".format( google_project_id ) ) try: sa_validity = GoogleServiceAccountValidity( sa_email, google_project_id, google_project_number=google_project_number ) if is_google_managed_service_account(sa_email): sa_validity.check_validity( early_return=True, check_type=True, check_policy_accessible=True, check_external_access=False, ) else: sa_validity.check_validity( early_return=True, check_type=True, check_policy_accessible=True, check_external_access=True, ) except Exception as exc: # any issues, assume invalid # TODO not sure if this is the right way to handle this... logger.warning( "Service Account {} determined invalid due to unhandled exception: {}. " "Assuming service account is invalid.".format(sa_email, str(exc)) ) traceback.print_exc() sa_validity = None return sa_validity
def check_authorization(action, doc): metadata = doc['metadata'] if 'acls' not in metadata: raise Unauthorized("You don't have access permission on this file") set_acls = set(metadata['acls'].split(',')) if flask.g.token is None: given_acls = set(filter_auth_ids(action, flask.g.user.project_access)) else: given_acls = set( filter_auth_ids(action, flask.g.token['context']['user']['projects'])) return len(set_acls & given_acls) > 0
def resolve_url(url, location, expires, action): protocol = location.scheme if protocol == 's3': aws_creds = current_app.config['AWS_CREDENTIALS'] if 'AWS_CREDENTIALS' in current_app.config and len(aws_creds) > 0: buckets = flask.current_app.config['S3_BUCKETS'] if location.netloc not in buckets.keys(): raise Unauthorized('permission denied for bucket') if buckets[location.netloc] not in aws_creds: raise Unauthorized('permission denied for bucket') credential_key = buckets[location.netloc] url = current_app.boto.presigned_url( location.netloc, location.path.strip('/'), expires, aws_creds[credential_key], ACTION_DICT[protocol][action], ) elif protocol not in ['http', 'https']: raise NotSupported('protocol {} in URL {} is not supported'.format( protocol, url)) return flask.jsonify(dict(url=url))
def check_auth(self, provider, user): """ check if the user should be authorized to storage resources """ storage_access = any( ["read-storage" in item for item in user.project_access.values()]) backend_access = any([ sa.provider.name == provider for p in user.projects.values() for sa in p.storage_access ]) if storage_access and backend_access: return True else: raise Unauthorized("Your are not authorized")
def test_prompt_login_no_authn(client, oauth_client): """ Test ``prompt=login`` when unable to re-AuthN. """ data = {'prompt': 'login'} with patch('fence.blueprints.oauth2.handle_login') as handle_login_mock: handle_login_mock.side_effect = Unauthorized('couldnt authN') auth_response = oauth2.get_authorize(client, oauth_client, data=data) assert auth_response.status_code == 302 assert 'Location' in auth_response.headers query_params = parse_qs( urlparse(auth_response.headers['Location']).query) assert 'error' in query_params assert query_params['error'][0] == 'access_denied'
def get(self): """ Complete the shibboleth login. """ if "SHIBBOLETH_HEADER" in config: eppn = flask.request.headers.get(config["SHIBBOLETH_HEADER"]) else: raise InternalError("Missing shibboleth header configuration") username = eppn.split("!")[-1] if eppn else None if username: login_user(flask.request, username, IdentityProvider.itrust) if flask.session.get("redirect"): return flask.redirect(flask.session.get("redirect")) return "logged in" else: raise Unauthorized("Please login")