def logout(self, state, client=None, post_logout_redirect_uri=''): """ Does a RP initiated logout from an OP. After logout the user will be redirect by the OP to a URL of choice (post_logout_redirect_uri). :param state: Key to an active session :param client: Which client to use :param post_logout_redirect_uri: If a special post_logout_redirect_uri should be used :return: A US """ if client is None: client = self.get_client_from_session_key(state) try: srv = client.service['end_session'] except KeyError: raise OidcServiceError("Does not know how to logout") if post_logout_redirect_uri: request_args = { "post_logout_redirect_uri": post_logout_redirect_uri } else: request_args = {} resp = srv.get_request_parameters(state=state, request_args=request_args) return resp
def get_user_info(self, state, client=None, access_token='', **kwargs): """ use the access token previously acquired to get some userinfo :param client: A Client instance :param state: The state value, this is the key into the session data store :param access_token: An access token :param kwargs: Extra keyword arguments :return: A :py:class:`oidcmsg.oidc.OpenIDSchema` instance """ if not access_token: _arg = self.session_interface.multiple_extend_request_args( {}, state, ['access_token'], ['auth_response', 'token_response', 'refresh_token_response']) request_args = {'access_token': access_token} if client is None: client = self.get_client_from_session_key(state) resp = client.do_request('userinfo', state=state, request_args=request_args, **kwargs) if is_error_message(resp): raise OidcServiceError(resp['error']) return resp
def refresh_access_token(self, state, client=None, scope=''): """ Refresh an access token using a refresh_token. When asking for a new access token the RP can ask for another scope for the new token. :param client: A Client instance :param state: The state key (the state parameter in the authorization request) :param scope: What the returned token should be valid for. :return: A :py:class:`oidcmsg.oidc.AccessTokenResponse` instance """ if scope: req_args = {'scope': scope} else: req_args = {} if client is None: client = self.get_client_from_session_key(state) try: tokenresp = client.do_request( 'refresh_token', authn_method=self.get_client_authn_method(client, "token_endpoint"), state=state, request_args=req_args ) except Exception as err: message = traceback.format_exception(*sys.exc_info()) logger.error(message) raise else: if is_error_message(tokenresp): raise OidcServiceError(tokenresp['error']) return tokenresp
def get_access_token(self, state, client=None): """ Use the 'accesstoken' service to get an access token from the OP/AS. :param state: The state key (the state parameter in the authorization request) :param client: A Client instance :return: A :py:class:`oidcmsg.oidc.AccessTokenResponse` or :py:class:`oidcmsg.oauth2.AuthorizationResponse` """ logger.debug('get_accesstoken') if client is None: client = self.get_client_from_session_key(state) authorization_response = self.session_interface.get_item( AuthorizationResponse, 'auth_response', state) authorization_request = self.session_interface.get_item( AuthorizationRequest, 'auth_request', state) req_args = { 'code': authorization_response['code'], 'state': state, 'redirect_uri': authorization_request['redirect_uri'], 'grant_type': 'authorization_code', 'client_id': client.service_context.client_id, 'client_secret': client.service_context.client_secret } logger.debug('request_args: {}'.format(req_args)) try: tokenresp = client.do_request( 'accesstoken', request_args=req_args, authn_method=self.get_client_authn_method(client, "token_endpoint"), state=state ) except Exception as err: message = traceback.format_exception(*sys.exc_info()) logger.error(message) raise else: if is_error_message(tokenresp): raise OidcServiceError(tokenresp['error']) return tokenresp
def get_valid_access_token(self, state): """ Find me a valid access token :param state: :return: An access token if a valid one exists and when it expires. Otherwise raise exception. """ exp = 0 token = None indefinite = [] now = time_sans_frac() for cls, typ in [(AccessTokenResponse, 'refresh_token_response'), (AccessTokenResponse, 'token_response'), (AuthorizationResponse, 'auth_response')]: try: response = self.session_interface.get_item(cls, typ, state) except KeyError: pass else: try: access_token = response['access_token'] except: continue else: try: _exp = response['__expires_at'] except KeyError: # No expiry date, lives for ever indefinite.append((access_token, 0)) else: if _exp > now: # expires sometime in the future if _exp > exp: exp = _exp token = (access_token, _exp) if indefinite: return indefinite[0] else: if token: return token else: raise OidcServiceError('No valid access token')
def load_registration_response(client): """ If the client has been statically registered that information must be provided during the configuration. If expected to be done dynamically. This method will do dynamic client registration. :param client: A :py:class:`oidcservice.oidc.Client` instance """ if not client.service_context.client_id: try: response = client.do_request('registration') except KeyError: raise ConfigurationError('No registration info') except Exception as err: logger.error(err) raise else: if 'error' in response: raise OidcServiceError(response.to_json())
def dynamic_provider_info_discovery(client): """ This is about performing dynamic Provider Info discovery :param client: A :py:class:`oidcservice.oidc.Client` instance """ try: client.service['provider_info'] except KeyError: raise ConfigurationError('Can not do dynamic provider info discovery') else: try: client.service_context.set( 'issuer', client.service_context.config['srv_discovery_url']) except KeyError: pass response = client.do_request('provider_info') if is_error_message(response): raise OidcServiceError(response['error'])
def _verify_issuer(self, resp, issuer): _pcr_issuer = resp["issuer"] if resp["issuer"].endswith("/"): if issuer.endswith("/"): _issuer = issuer else: _issuer = issuer + "/" else: if issuer.endswith("/"): _issuer = issuer[:-1] else: _issuer = issuer # In some cases we can live with the two URLs not being # the same. But this is an excepted that has to be explicit try: self.service_context.allow['issuer_mismatch'] except KeyError: if _issuer != _pcr_issuer: raise OidcServiceError( "provider info issuer mismatch '%s' != '%s'" % (_issuer, _pcr_issuer)) return _issuer
def parse_request_response(self, service, reqresp, response_body_type='', state="", **kwargs): """ Deal with a self.http response. The response are expected to follow a special pattern, having the attributes: - headers (list of tuples with headers attributes and their values) - status_code (integer) - text (The text version of the response) - url (The calling URL) :param service: A :py:class:`oidcservice.service.Service` instance :param reqresp: The HTTP request response :param response_body_type: If response in body one of 'json', 'jwt' or 'urlencoded' :param state: Session identifier :param kwargs: Extra keyword arguments :return: """ # if not response_body_type: # response_body_type = self.response_body_type if reqresp.status_code in SUCCESSFUL: logger.debug('response_body_type: "{}"'.format(response_body_type)) _deser_method = get_deserialization_method(reqresp) if _deser_method != response_body_type: logger.warning('Not the body type I expected: {} != {}'.format( _deser_method, response_body_type)) if _deser_method in ['json', 'jwt', 'urlencoded']: value_type = _deser_method else: value_type = response_body_type logger.debug('Successful response: {}'.format(reqresp.text)) try: return service.parse_response(reqresp.text, value_type, state, **kwargs) except Exception as err: logger.error(err) raise elif reqresp.status_code in [302, 303]: # redirect return reqresp elif reqresp.status_code == 500: logger.error("(%d) %s" % (reqresp.status_code, reqresp.text)) raise ParseError("ERROR: Something went wrong: %s" % reqresp.text) elif 400 <= reqresp.status_code < 500: logger.error('Error response ({}): {}'.format( reqresp.status_code, reqresp.text)) # expecting an error response _deser_method = get_deserialization_method(reqresp) if not _deser_method: _deser_method = 'json' try: err_resp = service.parse_response(reqresp.text, _deser_method) except FormatError: if _deser_method != response_body_type: try: err_resp = service.parse_response( reqresp.text, response_body_type) except (OidcServiceError, FormatError): raise OidcServiceError( "HTTP ERROR: %s [%s] on %s" % (reqresp.text, reqresp.status_code, reqresp.url)) else: raise OidcServiceError( "HTTP ERROR: %s [%s] on %s" % (reqresp.text, reqresp.status_code, reqresp.url)) except JSONDecodeError: # So it's not JSON assume text then err_resp = {'error': reqresp.text} err_resp['status_code'] = reqresp.status_code return err_resp else: logger.error('Error response ({}): {}'.format( reqresp.status_code, reqresp.text)) raise OidcServiceError( "HTTP ERROR: %s [%s] on %s" % (reqresp.text, reqresp.status_code, reqresp.url))