def revoke_token(self, refresh_token: str):
        """
        Revoke a refresh token

        @returns dictionary containing status of the operation
        @raises Exception in case of error
        """
        if OAuth2Session is None or refresh_token is None:
            raise ImportError(
                "No module named OAuth2Session or revoke_token not provided")

        provider = CONFIG_OBJ.get_oauth_provider()
        providers = CONFIG_OBJ.get_providers()

        auth = providers[provider][self.CLIENT_ID] + ":" + providers[provider][
            self.CLIENT_SECRET]
        encoded_auth = base64.b64encode(bytes(auth, self.UTF_8))

        headers = {
            'Accept': 'application/json',
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': 'Basic ' + str(encoded_auth, self.UTF_8)
        }

        data = f"token={refresh_token}&token_type_hint=refresh_token"

        response = requests.post(providers[provider][self.REVOKE_URI],
                                 headers=headers,
                                 data=data)
        self.log.debug("Response Status=%d", response.status_code)
        self.log.debug("Response Reason=%s", response.reason)
        self.log.debug("Response content=%s", response.content)
        self.log.debug(str(response.content, self.UTF_8))
        if response.status_code != 200:
            raise OAuthCredMgrError("Refresh token could not be revoked!")
    def _add_fabric_claims(self):
        """
        Set the claims for the Token by adding membership, project and scope
        """
        sub = self.claims.get("sub")
        url = CONFIG_OBJ.get_pr_url()
        roles = None
        projects = None
        if CONFIG_OBJ.is_project_registry_enabled():
            project_registry = ProjectRegistry(api_server=url, cookie=self._get_vouch_cookie(),
                                               cookie_name=CONFIG_OBJ.get_vouch_cookie_name(),
                                               cookie_domain=CONFIG_OBJ.get_vouch_cookie_domain_name())
            roles, projects = project_registry.get_projects_and_roles(sub)
        else:
            email = self.claims.get("email")
            roles, projects = CmLdapMgrSingleton.get().get_active_projects_and_roles_from_ldap(eppn=None, email=email)

        LOG.debug("Projects: %s, Roles: %s", projects, roles)

        projects_to_be_removed = []
        for project in projects.keys():
            LOG.debug("Processing %s", project)
            if self.project != "all" and self.project not in project:
                projects_to_be_removed.append(project)
        for x in projects_to_be_removed:
            projects.pop(x)

        if len(projects) < 1:
            raise TokenError("User is not a member of any of the project")
        self.claims['projects'] = projects
        self.claims["roles"] = roles
        self.claims["scope"] = self.scope
        LOG.debug("Claims %s", self.claims)
        self.unset = False
Beispiel #3
0
def authorize(request):
    ci_logon_id_token = request.headers.get(VOUCH_ID_TOKEN, None)
    refresh_token = request.headers.get(VOUCH_REFRESH_TOKEN, None)
    cookie_name = CONFIG_OBJ.get_vouch_cookie_name()
    cookie = request.cookies.get(cookie_name)

    return ci_logon_id_token, refresh_token, cookie
Beispiel #4
0
    def get_projects_and_roles(self, sub: str):
        """
        Determine Role from Project Registry
        :param sub: OIDC claim sub
        :param returns the roles and project with tags

        :returns a tuple containing user specific roles and project tags
        """
        if self.api_server is None or self.cookie is None:
            raise ProjectRegistryError(f"Project Registry URL: {self.api_server} or "
                                       "Cookie: {self.cookie} not available")

        # Create Session
        s = requests.Session()

        # Set the Cookie
        cookie_obj = requests.cookies.create_cookie(
            name=self.cookie_name,
            value=self.cookie
        )
        s.cookies.set_cookie(cookie_obj)
        LOG.debug(f"Using vouch cookie: {s.cookies}")

        # Set the headers
        headers = {
            'Accept': 'application/json',
            'Content-Type': "application/json"
        }
        s.headers.update(headers)

        # Get User by OIDC SUB Claim
        url = self.api_server + "/people/oidc_claim_sub?oidc_claim_sub={}".format(sub)
        ssl_verify = CONFIG_OBJ.is_pr_ssl_verify()
        response = s.get(url, verify=ssl_verify)

        if response.status_code != 200:
            raise ProjectRegistryError(f"Project Registry error occurred "
                                       f"status_code: {response.status_code} message: {response.content}")

        LOG.debug(f"Response : {response.json()}")

        roles = response.json().get('roles', None)
        projects = response.json().get('projects', None)

        # Get Per Project Tags
        project_tags = {}
        for p in projects:
            project_name = p.get('name', None)
            project_uuid = p.get('uuid', None)
            LOG.debug(f"Getting tags for Project: {project_name}")
            url = self.api_server + "/projects/{}".format(project_uuid)
            response = s.get(url, verify=ssl_verify)
            if response.status_code != 200:
                raise ProjectRegistryError(f"Project Registry error occurred "
                                           f"status_code: {response.status_code} message: {response.content}")
            project_tags[project_name] = response.json().get('tags', None)
        return roles, project_tags
    def _generate_fabric_token(self,
                               ci_logon_id_token: str,
                               project: str,
                               scope: str,
                               cookie: str = None):
        self.log.debug("CILogon Token: %s", ci_logon_id_token)
        # validate the token
        if jwt_validator is not None:
            LOG.info("Validating CI Logon token")
            code, claims_or_exception = jwt_validator.validate_jwt(
                token=ci_logon_id_token)
            if code is not ValidateCode.VALID:
                LOG.error(
                    f"Unable to validate provided token: {code}/{claims_or_exception}"
                )
                raise claims_or_exception

            fabric_token_encoder = FabricTokenEncoder(
                id_token=ci_logon_id_token,
                idp_claims=claims_or_exception,
                project=project,
                scope=scope,
                cookie=cookie)

            validity = CONFIG_OBJ.get_token_life_time()
            private_key = CONFIG_OBJ.get_jwt_private_key()
            pass_phrase = CONFIG_OBJ.get_jwt_private_key_pass_phrase()
            kid = CONFIG_OBJ.get_jwt_public_key_kid()

            return fabric_token_encoder.encode(private_key=private_key,
                                               validity_in_seconds=validity,
                                               kid=kid,
                                               pass_phrase=pass_phrase)
        else:
            LOG.warning(
                "JWT Token validator not initialized, skipping validation")

        return None
    def _get_vouch_cookie(self) -> str:
        vouch_cookie_enabled = CONFIG_OBJ.is_vouch_cookie_enabled()
        if not vouch_cookie_enabled or self.cookie is not None:
            return self.cookie

        vouch_secret = CONFIG_OBJ.get_vouch_secret()
        vouch_compression = CONFIG_OBJ.is_vouch_cookie_compressed()
        vouch_claims = CONFIG_OBJ.get_vouch_custom_claims()
        vouch_cookie_lifetime = CONFIG_OBJ.get_vouch_cookie_lifetime()
        vouch_helper = VouchEncoder(secret=vouch_secret, compression=vouch_compression)

        custom_claims = []
        for c in vouch_claims:
            c_type = c.strip().upper()

            if c_type == CustomClaimsType.OPENID.name:
                custom_claims.append(CustomClaimsType.OPENID)

            if c_type == CustomClaimsType.EMAIL.name:
                custom_claims.append(CustomClaimsType.EMAIL)

            if c_type == CustomClaimsType.PROFILE.name:
                custom_claims.append(CustomClaimsType.PROFILE)

            if c_type == CustomClaimsType.CILOGON_USER_INFO.name:
                custom_claims.append(CustomClaimsType.CILOGON_USER_INFO)

        p_tokens = PTokens(id_token=self.id_token, idp_claims=self.claims)

        code, cookie_or_exception = vouch_helper.encode(custom_claims_type=custom_claims, p_tokens=p_tokens,
                                                        validity_in_seconds=vouch_cookie_lifetime)

        if code != ValidateCode.VALID:
            LOG.error(f"Failed to encode the Vouch Cookie: {cookie_or_exception}")
            raise cookie_or_exception

        return cookie_or_exception
Beispiel #7
0
def main():
    """
    Main Entry Point
    """
    log = LOG
    try:
        app = connexion.App(__name__, specification_dir='swagger/')
        app.app.json_encoder = encoder.JSONEncoder
        app.add_api('swagger.yaml',
                    arguments={'title': 'Fabric Credential Manager API'},
                    pythonic_params=True)
        port = CONFIG_OBJ.get_rest_port()

        # prometheus server
        prometheus_port = CONFIG_OBJ.get_prometheus_port()
        prometheus_client.start_http_server(prometheus_port)

        # Start up the server to expose the metrics.
        waitress.serve(app, port=port)

    except Exception as ex:
        log.error("Exception occurred while starting Flask app")
        log.error(ex)
        raise ex
    def __init__(self):
        self.lock = threading.Lock()
        self.ldap_host = CONFIG_OBJ.get_ldap_host()
        self.ldap_user = CONFIG_OBJ.get_ldap_user()
        self.ldap_password = CONFIG_OBJ.get_ldap_pwd()
        self.ldap_search_base = CONFIG_OBJ.get_ldap_search_base()

        self.project_ignore_list = CONFIG_OBJ.get_project_ignore_list()
        self.roles_list = CONFIG_OBJ.get_roles()

        self.server = Server(host=self.ldap_host, use_ssl=True, get_info=ALL)
Beispiel #9
0
def get_logger():
    """
    Detects the path and level for the log file from the credmgr CONFIG_OBJ and sets
    up a logger. Instead of detecting the path and/or level from the
    credmgr CONFIG_OBJ, a custom path and/or level for the log file can be passed as
    optional arguments.

    :param log_path: Path to custom log file
    :param log_level: Custom log level
    :return: logging.Logger object
    """

    # Get the log path
    log_path = CONFIG_OBJ.get_logger_dir() + '/' + CONFIG_OBJ.get_logger_file()
    if log_path is None:
        raise RuntimeError(
            'The log file path must be specified in CONFIG_OBJ or passed as an argument'
        )

    # Get the log level
    log_level = CONFIG_OBJ.get_logger_level()
    if log_level is None:
        log_level = logging.INFO

    logger = CONFIG_OBJ.get_logger_name()

    # Set up the root logger
    log = logging.getLogger(logger)
    log.setLevel(log_level)
    log_format = '%(asctime)s - %(name)s - {%(filename)s:%(lineno)d} - %(levelname)s - %(message)s'
    os.makedirs(os.path.dirname(log_path), exist_ok=True)

    file_handler = RotatingFileHandler(
        log_path,
        backupCount=CONFIG_OBJ.get_logger_retain(),
        maxBytes=CONFIG_OBJ.get_logger_size())

    logging.basicConfig(handlers=[file_handler], format=log_format)

    return log, file_handler
Beispiel #10
0
from fss_utils.jwt_validate import JWTValidator
import prometheus_client

from fabric_cm.credmgr.config import CONFIG_OBJ
from fabric_cm.credmgr.logging import LOG

received_counter = prometheus_client.Counter('Requests_Received',
                                             'HTTP Requests',
                                             ['method', 'endpoint'])
success_counter = prometheus_client.Counter('Requests_Success', 'HTTP Success',
                                            ['method', 'endpoint'])
failure_counter = prometheus_client.Counter('Requests_Failed', 'HTTP Failures',
                                            ['method', 'endpoint'])

# initialize CI Logon Token Validation
CILOGON_CERTS = CONFIG_OBJ.get_oauth_jwks_url()
CILOGON_KEY_REFRESH = CONFIG_OBJ.get_oauth_key_refresh()
LOG.info(f'Initializing JWT Validator to use {CILOGON_CERTS} endpoint, '
         f'refreshing keys every {CILOGON_KEY_REFRESH} HH:MM:SS')
jwt_validator = JWTValidator(url=CILOGON_CERTS,
                             refresh_period=timedelta(
                                 hours=CILOGON_KEY_REFRESH.hour,
                                 minutes=CILOGON_KEY_REFRESH.minute,
                                 seconds=CILOGON_KEY_REFRESH.second),
                             audience=CONFIG_OBJ.get_oauth_client_id())

kid = CONFIG_OBJ.get_jwt_public_key_kid()
public_key = CONFIG_OBJ.get_jwt_public_key()
code, jwk_public_key_rsa = JWTManager.encode_jwk(key_file_name=public_key,
                                                 kid=kid,
                                                 alg="RS256")
 def validate_scope(scope: str):
     allowed_scopes = CONFIG_OBJ.get_allowed_scopes()
     if scope not in allowed_scopes:
         raise OAuthCredMgrError(
             f"Scope {scope} is not allowed! Allowed scope values: {allowed_scopes}"
         )
    def refresh_token(self,
                      refresh_token: str,
                      project: str,
                      scope: str,
                      cookie: str = None) -> dict:
        """
        Refreshes a token from CILogon and generates Fabric token using project and scope saved in Database

        @param project: Project for which token is requested, by default it is set to 'all'
        @param scope: Scope of the requested token, by default it is set to 'all'
        @param refresh_token: Refresh Token
        @param cookie: Vouch Proxy Cookie
        @returns dict containing id_token and refresh_token

        @raises Exception in case of error
        """

        self.validate_scope(scope=scope)

        if OAuth2Session is None or refresh_token is None:
            raise ImportError(
                "No module named OAuth2Session or refresh_token not provided")

        provider = CONFIG_OBJ.get_oauth_provider()
        providers = CONFIG_OBJ.get_providers()

        refresh_token_dict = {self.REFRESH_TOKEN: refresh_token}
        self.log.debug(f"Incoming refresh_token: {refresh_token}")

        # refresh the token (provides both new refresh and access tokens)
        oauth_client = OAuth2Session(providers[provider][self.CLIENT_ID],
                                     token=refresh_token_dict)
        new_token = oauth_client.refresh_token(
            providers[provider][self.TOKEN_URI],
            client_id=providers[provider][self.CLIENT_ID],
            client_secret=providers[provider][self.CLIENT_SECRET])

        new_refresh_token = None
        try:
            new_refresh_token = new_token.pop(self.REFRESH_TOKEN)
            id_token = new_token.pop(self.ID_TOKEN)
        except KeyError:
            self.log.error("No refresh or id token returned")
            raise OAuthCredMgrError("No refresh or id token returned")
        self.log.debug(f"new_refresh_token: {new_refresh_token}")

        try:
            id_token = self._generate_fabric_token(ci_logon_id_token=id_token,
                                                   project=project,
                                                   scope=scope,
                                                   cookie=cookie)
            result = {
                self.ID_TOKEN: id_token,
                self.REFRESH_TOKEN: new_refresh_token
            }

            return result
        except Exception as e:
            self.log.error(
                f"Exception error while generating Fabric Token: {e}")
            self.log.error(
                f"Failed generating the token but still returning refresh token"
            )
            exception_string = str(e)
            if exception_string.__contains__(
                    "could not be associated with a pending flow"):
                exception_string = "Specified refresh token is expired and can not be found in the database."
            error_string = f"error: {exception_string}, {self.REFRESH_TOKEN}: {new_refresh_token}"
            raise OAuthCredMgrError(error_string)