Example #1
0
    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
Example #2
0
    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)
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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