def handle_authn_response(self, context, internal_resp): """ See super class method satosa.frontends.base.FrontendModule#handle_authn_response :type context: satosa.context.Context :type internal_response: satosa.internal.InternalData :rtype oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(context.state) claims = self.converter.from_internal("openid", internal_resp.attributes) # Filter unset claims claims = {k: v for k, v in claims.items() if v} self.user_db[internal_resp.subject_id] = dict( combine_claim_values(claims.items())) auth_resp = self.provider.authorize( auth_req, internal_resp.subject_id, extra_id_token_claims=lambda user_id, client_id: self. _get_extra_id_token_claims(user_id, client_id), ) if self.stateless: del self.user_db[internal_resp.subject_id] del context.state[self.name] http_response = auth_resp.request(auth_req["redirect_uri"], should_fragment_encode(auth_req)) return SeeOther(http_response)
def authentication_endpoint(): # parse authentication request try: auth_req = current_app.provider.parse_authentication_request(flask.request.get_data().decode('utf-8'), flask.request.headers) current_app.authn_requests[auth_req['nonce']] = auth_req.to_dict() # Check client vetting method client = current_app.provider.clients[auth_req['client_id']] if client.get('vetting_policy') == 'POST_AUTH': # Return a authn response immediately authn_response = create_authentication_response(auth_req) response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) try: headers = {'Authorization': 'Bearer {}'.format(auth_req['token'])} except KeyError: # Bearer Token needs to be supplied with the auth request for instant responses raise InvalidAuthenticationRequest('Token missing', auth_req) current_app.authn_response_queue.enqueue(deliver_response_task, response_url, headers=headers) except InvalidAuthenticationRequest as e: current_app.logger.debug('received invalid authn request', exc_info=True) error_url = e.to_error_url() if error_url: current_app.authn_response_queue.enqueue(deliver_response_task, error_url) return make_response('OK', 200) else: # deliver directly to client since we're only supporting POST return make_response('Something went wrong: {}'.format(str(e)), 400) return make_response('OK', 200)
def authentication_endpoint(): if flask.request.method == 'GET': # parse authentication request try: from pprint import pprint auth_req = current_app.provider.parse_authentication_request(urlencode(flask.request.args), flask.request.headers) flask.session['auth_req'] = auth_req except InvalidAuthenticationRequest as e: current_app.logger.debug('received invalid authn request', exc_info=True) error_url = e.to_error_url() if error_url: return redirect(error_url, 303) else: # show error to user return make_response('Something went wrong: {}'.format(str(e)), 400) return render_template('login.jinja2') else: if 'auth_req' not in flask.session: return make_response('Could not get the authentication request from the session', 400) auth_req = flask.session['auth_req'] auth_provider = LDAPAuthenticator() if auth_provider.authenticate({'username': flask.request.form['username'], 'password': flask.request.form['password']}): authn_response = current_app.provider.authorize(auth_req, flask.request.form['username']) response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) else: response_url = '{0}?error=access_denied&state={1}'.format(auth_req['redirect_uri'], auth_req['state']) return redirect(response_url, 303)
def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None): """ See super class method satosa.frontends.base.FrontendModule#handle_authn_response :type context: satosa.context.Context :type internal_response: satosa.internal.InternalData :rtype oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(context.state) attributes = self.converter.from_internal("openid", internal_resp.attributes) self.user_db[internal_resp.subject_id] = { k: v[0] for k, v in attributes.items() } auth_resp = self.provider.authorize(auth_req, internal_resp.subject_id, extra_id_token_claims) del context.state[self.name] http_response = auth_resp.request(auth_req["redirect_uri"], should_fragment_encode(auth_req)) return SeeOther(http_response)
def authorize(): id_token = session['id_token'] auth_request = AuthorizationRequest(**session['auth_request']) auth_response = current_app.provider.authorize(auth_request, id_token['sub']) return auth_response.request(auth_request['redirect_uri'], should_fragment_encode(auth_request))
def handle_backend_error(self, exception): """ See super class satosa.frontends.base.FrontendModule :type exception: satosa.exception.SATOSAError :rtype: oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(exception.state) error_resp = AuthorizationErrorResponse(error="access_denied", error_description=exception.message) satosa_logging(logger, logging.DEBUG, exception.message, exception.state) return SeeOther(error_resp.request(auth_req["redirect_uri"], should_fragment_encode(auth_req)))
def handle_backend_error(self, exception): """ See super class satosa.frontends.base.FrontendModule :type exception: satosa.exception.SATOSAError :rtype: oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(exception.state) # If the client sent us a state parameter, we should reflect it back according to the spec if 'state' in auth_req: error_resp = AuthorizationErrorResponse(error="access_denied", error_description=exception.message, state=auth_req['state']) else: error_resp = AuthorizationErrorResponse(error="access_denied", error_description=exception.message) satosa_logging(logger, logging.DEBUG, exception.message, exception.state) return SeeOther(error_resp.request(auth_req["redirect_uri"], should_fragment_encode(auth_req)))
def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None): """ See super class method satosa.frontends.base.FrontendModule#handle_authn_response :type context: satosa.context.Context :type internal_response: satosa.internal_data.InternalResponse :rtype oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(context.state) attributes = self.converter.from_internal("openid", internal_resp.attributes) self.user_db[internal_resp.user_id] = {k: v[0] for k, v in attributes.items()} auth_resp = self.provider.authorize(auth_req, internal_resp.user_id, extra_id_token_claims) del context.state[self.name] http_response = auth_resp.request(auth_req["redirect_uri"], should_fragment_encode(auth_req)) return SeeOther(http_response)
def handle_authn_response(self, context, internal_resp): auth_req = self._get_authn_request_from_state(context.state) affiliation_attribute = self.converter.from_internal( 'openid', internal_resp.attributes)['affiliation'] scope = auth_req['scope'] matching_affiliation = get_matching_affiliation( scope, affiliation_attribute) if matching_affiliation: return super().handle_authn_response( context, internal_resp, {'auth_time': internal_resp.auth_info.timestamp}) auth_error = AuthorizationErrorResponse(error='access_denied') del context.state[self.name] http_response = auth_error.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) return SeeOther(http_response)
def handle_authn_response(self, context, internal_resp, extra_id_token_claims=None): """ See super class method satosa.frontends.base.FrontendModule#handle_authn_response :type context: satosa.context.Context :type internal_response: satosa.internal_data.InternalResponse :rtype oic.utils.http_util.Response """ auth_req = self._get_authn_request_from_state(context.state) attributes = self.converter.from_internal("openid", internal_resp.attributes) ### rZone Code Start ### # client에게 허용되는 scope 처리 ''' Original Code self.user_db[internal_resp.user_id] = {k: v[0] for k, v in attributes.items()} ''' allowed = self.internal_req.approved_attributes allowed_oidc = [] for claim_name in allowed: if claim_name in self.internal_attributes['attributes']: if 'openid' in self.internal_attributes['attributes'][ claim_name]: for oidc_attr in self.internal_attributes['attributes'][ claim_name]['openid']: allowed_oidc.append(oidc_attr) _userinfo = {} for k, v in attributes.items(): if k in allowed_oidc: _userinfo[k] = ' '.join(v) self.user_db[internal_resp.user_id] = _userinfo ### rZone code End ### auth_resp = self.provider.authorize(auth_req, internal_resp.user_id, extra_id_token_claims) del context.state[self.name] http_response = auth_resp.request(auth_req["redirect_uri"], should_fragment_encode(auth_req)) return SeeOther(http_response)
def delayed_authn_response_task(nonce, bearer_token, identity, app_config=None): """ :param nonce: Nonce from QR data :type nonce: str :param bearer_token: Token from QR data :type bearer_token: str :param identity: Users identity :type identity: str :param app_config: Minimal config for making tests work :type app_config: :return: :rtype: """ auth_req = AuthorizationRequest(**nonce) app = oidc_provider_init_app(__name__, config=app_config) with app.app_context(): authn_response = create_authentication_response(auth_req, identity, extra_userinfo) response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) headers = {'Authorization': 'Bearer {}'.format(bearer_token)} deliver_response_task(response_url, headers=headers)
def authentication_endpoint(): # parse authentication request print(request) print(request.headers) try: auth_req = current_app.provider.parse_authentication_request(urlencode(flask.request.args), flask.request.args) except InvalidAuthenticationRequest as e: current_app.logger.debug('received invalid authn request', exc_info=True) error_url = e.to_error_url() if error_url: return redirect(error_url, 303) else: # show error to user return make_response('Something went wrong: {}'.format(str(e)), 400) # automatically authentication authn_response = current_app.provider.authorize(auth_req, str(current_user.username)) response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) return redirect(response_url, 303)
def handle_authn_response(self, context, internal_resp): auth_req = self._get_authn_request_from_state(context.state) # User might not give us consent to release affiliation if 'affiliation' in internal_resp.attributes: affiliation_attribute = self.converter.from_internal('openid', internal_resp.attributes)['affiliation'] scope = auth_req['scope'] matching_affiliation = get_matching_affiliation(scope, affiliation_attribute) if matching_affiliation: return super().handle_authn_response(context, internal_resp, {'auth_time': internal_resp.auth_info.timestamp, 'requested_scopes': {'values': scope}}) # User's affiliation was not released or was not the one requested so return an error # If the client sent us a state parameter, we should reflect it back according to the spec if 'state' in auth_req: auth_error = AuthorizationErrorResponse(error='access_denied', state=auth_req['state']) else: auth_error = AuthorizationErrorResponse(error='access_denied') del context.state[self.name] http_response = auth_error.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) return SeeOther(http_response)
def log_user(): auth_provider = current_app.sql_backend if auth_provider.authenticate({ "username": flask.request.form["username"], "password": flask.request.form["password"] }): current_app.logger.debug("authentification de l'utlisateur") authn_response = current_app.provider.authorize( flask.session["auth_req"], flask.request.form["username"]) current_app.logger.debug("authn_response %s " % authn_response) flask.session["authn_response"] = authn_response current_app.logger.debug(request.args.keys()) if not auth_provider.user_have_constented_scope( request.args.get("scope")): return render_template("scope_claims.jinja2", app_name=request.args.get("client_id"), scope=request.args.get("scope"), args=request.query_string) response_url = flask.session["authn_response"].request( flask.session["auth_req"]["redirect_uri"], should_fragment_encode(flask.session["auth_req"])) return redirect(response_url, 303)
def handle_authn_response(self, context, internal_resp): auth_req = self._get_authn_request_from_state(context.state) resp_rp = auth_req.get('client_id') # User might not give us consent to release affiliation if 'affiliation' in internal_resp.attributes: affiliation_attribute = self.converter.from_internal('openid', internal_resp.attributes)['affiliation'] scope = auth_req['scope'] matching_affiliation = get_matching_affiliation(scope, affiliation_attribute) if matching_affiliation: transaction_log(context.state, self.config.get("response_exit_order", 1200), "inacademia_frontend", "response", "exit", "success", resp_rp , '', 'Responding successful validation to RP') return super().handle_authn_response(context, internal_resp, {'auth_time': parser.parse(internal_resp.auth_info.timestamp).timestamp(), 'requested_scopes': {'values': scope}}) # User's affiliation was not released or was not the one requested so return an error # If the client sent us a state parameter, we should reflect it back according to the spec transaction_log(context.state, self.config.get("response_exit_order", 1210), "inacademia_frontend", "response", "exit", "failed", resp_rp, '', ErrorDescription.REQUESTED_AFFILIATION_MISMATCH[LOG_MSG]) if 'state' in auth_req: auth_error = AuthorizationErrorResponse(error='access_denied', state=auth_req['state'], error_description=ErrorDescription.REQUESTED_AFFILIATION_MISMATCH[ ERROR_DESC]) else: auth_error = AuthorizationErrorResponse(error='access_denied', error_description=ErrorDescription.REQUESTED_AFFILIATION_MISMATCH[ ERROR_DESC]) del context.state[self.name] http_response = auth_error.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) return SeeOther(http_response)
def test_by_response_type(self, response_type, expected): auth_req = {'response_type': response_type} assert should_fragment_encode( AuthorizationRequest(**auth_req)) is expected
def consent(): response_url = flask.session["authn_response"].request( flask.session["auth_req"]["redirect_uri"], should_fragment_encode(flask.session["auth_req"])) return redirect(response_url, 303)
def oidc_authorization(self) -> Union[str, Response]: person: Person = self.storage.session.query(Person).filter_by( uid=self.util.current_user()).scalar() if person.state != "active": logging.warning( "User {} tried to log in via OIDC without membership!", person.uid) self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Missing membership!", "OpenID Connect login was successful, but active " + "membership is required to log in to 3rd party services.", )) return render_template("oidc.html") try: # Override claims to always only and at least return email for CF authn_req = AuthorizationRequest().from_dict({ **request.args, "claims": { "id_token": { "email": { "essential": True } } } }) logging.debug(authn_req) authn_req.verify() if authn_req["client_id"] not in self.provider.clients: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Invalid Client", "Unknown Client ID. All applications must be pre-registered.", )) return render_template("oidc.html") client = self.provider.clients[authn_req["client_id"]] if client["redirect_uri"] != authn_req["redirect_uri"]: self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "Incorrect Redirect URI", "Unauthorized redirect URI for this client.", )) return render_template("oidc.html") authn_response = self.provider.authorize( authn_req, str(self.util.current_user())) return_url = authn_response.request( authn_req["redirect_uri"], should_fragment_encode(authn_req)) return redirect(return_url) except Exception as err: # pylint: disable=broad-except logging.error(err) self.util.alert( KosekiAlert( KosekiAlertType.DANGER, "OIDC Error", "Error: {}".format(str(err)), )) return render_template("oidc.html")
def vetting_result(): if not current_app.config.get('FREJA_CALLBACK_X5T_CERT'): current_app.logger.info('Configuration error: FREJA_CALLBACK_X5T_CERT is not set') return make_response('Configuration error', 500) _freja_callback_x5t_cert = current_app.config.get('FREJA_CALLBACK_X5T_CERT') _freja_callback_x5t_pub_key = RSA.importKey(_freja_callback_x5t_cert) _freja_callback_rsa_pub_key = RSAKey() _freja_callback_rsa_pub_key.load_key(_freja_callback_x5t_pub_key) current_app.logger.debug('flask.request.headers: \'{!s}\''.format(flask.request.headers)) current_app.logger.debug('flask.request.data: \'{!s}\''.format(flask.request.get_data())) try: if flask.request.headers['Content-Type'] == 'application/jose': current_app.logger.info('Received a callback with MIME application/jose') else: current_app.logger.info('Received a callback with an invalid MIME: \'{!s}\'' .format(flask.request.headers['Content-Type'])) return make_response('Invalid MIME', 400) except KeyError: current_app.logger.info('Received a callback without a MIME') return make_response('No MIME specified', 400) try: data = flask.request.get_json(force=True) except: current_app.logger.info('Invalid verisec callback: missing or invalid JSON') return make_response('missing or invalid JSON', 400) if not data: current_app.logger.info('Invalid verisec callback: no JSON data provided') return make_response('Missing JSON data', 400) ia_response_data = data.get('iaResponseData') if not ia_response_data: current_app.logger.info('Missing iaResponseData in verisec callback: \'{!s}\''.format(data)) return make_response('Missing iaResponseData', 400) current_app.logger.info('Received verisec iaResponseData: \'{!s}\''.format(ia_response_data)) jws_parts = ia_response_data.split('.') # A correctly formatted JWS is made up of 3 parts if len(jws_parts) != 3: current_app.logger.info('iaResponseData response doesn\'t seems to be a JWS') return make_response('iaResponseData is not a JWS', 400) # This is for testing only and therefore we do not verify the JWS yet unverified_header = jws_parts[0] unverified_payload = jws_parts[1] # It should be possible to base64 decode the header and payload try: # urlsafe_b64decode returns bytes object so we decode to get str aka utf8 unverified_header_decoded = urlsafe_b64decode(unverified_header + '=' * (4 - (len(unverified_header) % 4))).decode('utf8') unverified_payload_decoded = urlsafe_b64decode(unverified_payload + '=' * (4 - (len(unverified_payload) % 4))).decode('utf8') except UnicodeDecodeError: current_app.logger.info('Couldn\'t urlsafe_b64decode iaResponseData because it contains invalid UTF-8') return make_response('Incorrect UTF-8 in iaResponseData', 400) except binascii.Error: current_app.logger.info('Couldn\'t urlsafe_b64decode iaResponseData because non-base64 digit found') return make_response('Incorrect UTF-8 in iaResponseData', 400) except TypeError: current_app.logger.info('Couldn\'t urlsafe_b64decode iaResponseData') return make_response('Incorrect base64 encoded iaResponseData', 400) try: json.loads(unverified_header_decoded) json.loads(unverified_payload_decoded) except JSONDecodeError: current_app.logger.info('Incorrect UTF-8 BOM or invalid JSON data from base64 decoded iaResponseData') return make_response('Incorrectly encoded JSON in base64 decoded iaResponseData', 400) except TypeError: current_app.logger.info('JSON in base64 decoded iaResponseData is not str') return make_response('Incorrectly encoded JSON in base64 decoded iaResponseData', 400) try: verified_payload = JWS().verify_compact(ia_response_data, keys=[_freja_callback_rsa_pub_key], sigalg='RS256') except BadSignature as e: current_app.logger.info('The JWS was not properly signed') return make_response('Invalid signature', 400) except Exception as e: current_app.logger.info(str(e)) return make_response('Invalid JWS', 400) try: verified_payload_country = verified_payload.pop('country') verified_payload_opaque = verified_payload.pop('opaque') # The opaque contains nonce and token verified_payload_ref = verified_payload.pop('ref') verified_payload_ssn = verified_payload.pop('ssn') except KeyError: current_app.logger.info('The verified JWS payload is missing some required claims') return make_response('The verified JWS payload is missing some required claims', 400) # Make sure that we have processed all claims in the payload if len(verified_payload) == 0: try: verified_opaque_deserialized = parse_opaque_data(verified_payload_opaque) except InvalidOpaqueDataError as e: # This is by design since we want the message from this exception return make_response(str(e), 400) if verified_opaque_deserialized['nonce'] == current_app.config.get('FREJA_TEST_NONCE', None): current_app.logger.info('Received a valid callback-test') return make_response('OK', 200) auth_req_data = current_app.authn_requests.pop(verified_opaque_deserialized['nonce'], None) if not auth_req_data: current_app.logger.info('Unknown nonce in verified JWS payload: \'{!s}\''.format(verified_opaque_deserialized['nonce'])) return make_response('Unknown nonce in verified JWS payload', 400) user_id = str(uuid.uuid4()) current_app.users[user_id] = { 'results': { 'freja_eid': { 'vetting_time': time.time(), 'country': verified_payload_country, 'opaque': verified_payload_opaque, 'ref': verified_payload_ref, 'ssn': verified_payload_ssn, } } } auth_req = AuthorizationRequest(**auth_req_data) authn_response = create_authentication_response(auth_req=auth_req, user_id=user_id, extra_userinfo=extra_userinfo) response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req)) headers = {'Authorization': 'Bearer {}'.format(verified_opaque_deserialized['token'])} current_app.authn_response_queue.enqueue(deliver_response_task, response_url, headers=headers) return make_response('OK', 200) current_app.logger.info('Received an invalid verisec callback') return make_response('Invalid request', 400)