def exchange_code_for_tokens(self, app_config, http_client, code, redirect_suffix): # Exchange the code for the access token and id_token try: json_data = self.exchange_code( app_config, http_client, code, redirect_suffix=redirect_suffix, form_encode=self.requires_form_encoding(), ) except OAuthExchangeCodeException as oce: raise OAuthLoginException(str(oce)) # Make sure we received both. access_token = json_data.get("access_token", None) if access_token is None: logger.debug("Missing access_token in response: %s", json_data) raise OAuthLoginException( "Missing `access_token` in OIDC response") id_token = json_data.get("id_token", None) if id_token is None: logger.debug("Missing id_token in response: %s", json_data) raise OAuthLoginException("Missing `id_token` in OIDC response") return id_token, access_token
def service_verify_user_info_for_login(self, app_config, http_client, token, user_info): # Retrieve the user's orgnizations (if organization filtering is turned on) if self.allowed_organizations() is None: return moondragon_media_type = { 'Accept': 'application/vnd.github.moondragon+json' } token_param = { 'access_token': token, } get_orgs = http_client.get(self.orgs_endpoint(), params=token_param, headers=moondragon_media_type) if get_orgs.status_code // 100 != 2: logger.debug('get_orgs response: %s', get_orgs.json()) raise OAuthLoginException( 'Got non-2XX response for org lookup: %s' % get_orgs.status_code) organizations = set( [org.get('login').lower() for org in get_orgs.json()]) matching_organizations = organizations & set( self.allowed_organizations()) if not matching_organizations: logger.debug('Found organizations %s, but expected one of %s', organizations, self.allowed_organizations()) err = """You are not a member of an allowed GitHub organization. Please contact your system administrator if you believe this is in error.""" raise OAuthLoginException(err)
def exchange_code_for_login(self, app_config, http_client, code, redirect_suffix): sub, lusername, email_address = super().exchange_code_for_login( app_config, http_client, code, redirect_suffix ) # Conduct RedHat Export Compliance if enabled if features.EXPORT_COMPLIANCE: logger.debug("Attempting to hit export compliance service") try: result = requests.post( app_config.get("EXPORT_COMPLIANCE_ENDPOINT"), cert=( "/conf/stack/export-compliance-client.crt", "/conf/stack/export-compliance-client.key", ), json={"user": {"login": lusername}, "account": {"primary": True}}, timeout=5, ) logger.debug("Got result from export compliance service: " + str(result.json())) # 200 => Endpoint was hit successfully and user was found # 400 => Endpoint was hit successfully but no user was found if ( result.status_code == 200 and result.json().get("result", "") == "ERROR_EXPORT_CONTROL" ): raise OAuthLoginException(str(result.json().get("description", ""))) except Exception as e: raise OAuthLoginException(str(e)) return sub, lusername, email_address
def get_verified_user_email(self, app_config, http_client, token, user_info): v3_media_type = {'Accept': 'application/vnd.github.v3'} token_param = { 'access_token': token, } # Find the e-mail address for the user: we will accept any email, but we prefer the primary get_email = http_client.get(self.email_endpoint(), params=token_param, headers=v3_media_type) if get_email.status_code // 100 != 2: raise OAuthLoginException( 'Got non-2XX status code for emails endpoint: %s' % get_email.status_code) verified_emails = [ email for email in get_email.json() if email['verified'] ] primary_emails = [ email for email in get_email.json() if email['primary'] ] # Special case: We don't care about whether an e-mail address is "verified" under GHE. if self.is_enterprise() and not verified_emails: verified_emails = primary_emails allowed_emails = (primary_emails or verified_emails or []) return allowed_emails[0]['email'] if len(allowed_emails) > 0 else None
def get_verified_user_email(self, app_config, http_client, token, user_info): v3_media_type = { "Accept": "application/vnd.github.v3", "Authorization": "token %s" % token, } # Find the e-mail address for the user: we will accept any email, but we prefer the primary get_email = http_client.get(self.email_endpoint(), headers=v3_media_type) if get_email.status_code // 100 != 2: raise OAuthLoginException( "Got non-2XX status code for emails endpoint: %s" % get_email.status_code) verified_emails = [ email for email in get_email.json() if email["verified"] ] primary_emails = [ email for email in get_email.json() if email["primary"] ] # Special case: We don't care about whether an e-mail address is "verified" under GHE. if self.is_enterprise() and not verified_emails: verified_emails = primary_emails allowed_emails = primary_emails or verified_emails or [] return allowed_emails[0]["email"] if len(allowed_emails) > 0 else None
def exchange_code_for_login(self, app_config, http_client, code, redirect_suffix): # Exchange the code for the access token and id_token id_token, access_token = self.exchange_code_for_tokens( app_config, http_client, code, redirect_suffix) # Decode the id_token. try: decoded_id_token = self.decode_user_jwt(id_token) except InvalidTokenError as ite: logger.exception("Got invalid token error on OIDC decode: %s", ite) raise OAuthLoginException("Could not decode OIDC token") except PublicKeyLoadException as pke: logger.exception( "Could not load public key during OIDC decode: %s", pke) raise OAuthLoginException("Could find public OIDC key") # If there is a user endpoint, use it to retrieve the user's information. Otherwise, we use # the decoded ID token. if self.user_endpoint(): # Retrieve the user information. try: user_info = self.get_user_info(http_client, access_token) except OAuthGetUserInfoException as oge: raise OAuthLoginException(str(oge)) else: user_info = decoded_id_token # Verify subs. if user_info["sub"] != decoded_id_token["sub"]: logger.debug( "Mismatch in `sub` returned by OIDC user info endpoint: %s vs %s", user_info["sub"], decoded_id_token["sub"], ) raise OAuthLoginException( "Mismatch in `sub` returned by OIDC user info endpoint") # Check if we have a verified email address. if self.config.get("VERIFIED_EMAIL_CLAIM_NAME"): email_address = user_info.get( self.config["VERIFIED_EMAIL_CLAIM_NAME"]) else: email_address = user_info.get("email") if user_info.get( "email_verified") else None logger.debug("Found e-mail address `%s` for sub `%s`", email_address, user_info["sub"]) if self._mailing: if email_address is None: raise OAuthLoginException( "A verified email address is required to login with this service" ) # Check for a preferred username. if self.config.get("PREFERRED_USERNAME_CLAIM_NAME"): lusername = user_info.get( self.config["PREFERRED_USERNAME_CLAIM_NAME"]) else: lusername = user_info.get("preferred_username") if lusername is None: # Note: Active Directory provides `unique_name` and `upn`. # https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-id-and-access-tokens lusername = user_info.get("unique_name", user_info.get("upn")) if lusername is None: lusername = user_info["sub"] if lusername.find("@") >= 0: lusername = lusername[0:lusername.find("@")] return decoded_id_token["sub"], lusername, email_address