예제 #1
0
    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)
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
    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)
예제 #5
0
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))
예제 #6
0
 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)))
예제 #7
0
 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)))
예제 #8
0
 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)))
예제 #9
0
    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)
예제 #10
0
    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)
예제 #11
0
    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)
예제 #12
0
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)
예제 #13
0
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)
예제 #14
0
    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)
예제 #15
0
파일: oidc.py 프로젝트: tom333/flask-op
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)
예제 #16
0
    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)
예제 #17
0
파일: test_util.py 프로젝트: virajs/pyop
 def test_by_response_type(self, response_type, expected):
     auth_req = {'response_type': response_type}
     assert should_fragment_encode(
         AuthorizationRequest(**auth_req)) is expected
예제 #18
0
파일: oidc.py 프로젝트: tom333/flask-op
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)
예제 #19
0
    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")
예제 #20
0
파일: views.py 프로젝트: SUNET/se-leg-op
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)