def encode(self, private_key: str, validity_in_seconds: int, kid: str, pass_phrase: str = None) -> str:
        """
        Generate Fabric Token by adding additional claims and signing with Fabric Cert
        :param private_key Private Key to sign the fabric_cm token
        :param validity_in_seconds Validity of the Token in seconds
        :param kid Public Key Id
        :param pass_phrase Pass Phrase for Private Key
        :return JWT String containing encoded Fabric Token
        """
        if self.encoded:
            return self.token

        self._add_fabric_claims()

        code, token_or_exception = JWTManager.encode_and_sign_with_private_key(validity=validity_in_seconds,
                                                                               claims=self.claims,
                                                                               private_key_file_name=private_key,
                                                                               pass_phrase=pass_phrase, kid=kid,
                                                                               algorithm='RS256')
        if code != ValidateCode.VALID:
            LOG.error(f"Failed to encode the Fabric Token: {token_or_exception}")
            raise token_or_exception

        self.token = token_or_exception
        self.encoded = True
        return self.token
def version_get():  # noqa: E501
    """version

    Version # noqa: E501


    :rtype: Version
    """
    received_counter.labels(HTTP_METHOD_GET, VERSION_URL).inc()
    try:
        version = '1.0.0'
        tag = '1.0.0'
        url = "https://api.github.com/repos/fabric-testbesd/CredentialManager/git/refs/tags/{}".format(
            tag)

        response = Version()
        response.version = version
        response.gitsha1 = 'Not Available'

        result = requests.get(url)
        if result.status_code == 200 and result.json() is not None:
            object_json = result.json().get("object", None)
            if object_json is not None:
                sha = object_json.get("sha", None)
                if sha is not None:
                    response.gitsha1 = sha
        success_counter.labels(HTTP_METHOD_GET, VERSION_URL).inc()
    except Exception as ex:
        LOG.exception(ex)
        failure_counter.labels(HTTP_METHOD_GET, VERSION_URL).inc()
        return cors_response(status=INTERNAL_SERVER_ERROR,
                             xerror=str(ex),
                             body=str(ex))
    return response
Пример #3
0
def tokens_refresh_post(body, project_name=None, scope=None):  # noqa: E501
    """Refresh FABRIC OAuth tokens for an user

    Request to refresh OAuth tokens for an user  # noqa: E501

    :param body:
    :type body: dict | bytes
    :param project_name: Project Name
    :type project_name: str
    :param scope: Scope for which token is requested
    :type scope: str

    :rtype: Success
    """
    received_counter.labels(HTTP_METHOD_POST, TOKENS_REFRESH_URL).inc()
    if connexion.request.is_json:
        body = Request.from_dict(connexion.request.get_json())  # noqa: E501
    try:
        ci_logon_id_token, refresh_token, cookie = authorize(connexion.request)
        credmgr = OAuthCredmgr()
        response = Success.from_dict(
            credmgr.refresh_token(refresh_token=body.refresh_token,
                                  project=project_name,
                                  scope=scope,
                                  cookie=cookie))
        success_counter.labels(HTTP_METHOD_POST, TOKENS_REFRESH_URL).inc()
        return response
    except Exception as ex:
        LOG.exception(ex)
        failure_counter.labels(HTTP_METHOD_POST, TOKENS_REFRESH_URL).inc()
        msg = str(ex).replace("\n", "")
        return cors_response(status=INTERNAL_SERVER_ERROR,
                             xerror=msg,
                             body=msg)
Пример #4
0
def tokens_create_post(project_name=None, scope=None):  # noqa: E501
    """Generate Fabric OAuth tokens for an user

    Request to generate Fabric OAuth tokens for an user  # noqa: E501

    :param project_name: Project Name
    :type project_name: str
    :param scope: Scope for which token is requested
    :type scope: str

    :rtype: Success
    """
    received_counter.labels(HTTP_METHOD_POST, TOKENS_CREATE_URL).inc()
    try:
        ci_logon_id_token, refresh_token, cookie = authorize(connexion.request)
        if ci_logon_id_token is None:
            return AUTHORIZATION_ERR, 401

        credmgr = OAuthCredmgr()
        result = credmgr.create_token(ci_logon_id_token=ci_logon_id_token,
                                      refresh_token=refresh_token,
                                      project=project_name,
                                      scope=scope,
                                      cookie=cookie)
        response = Success.from_dict(result)
        LOG.debug(result)
        success_counter.labels(HTTP_METHOD_POST, TOKENS_CREATE_URL).inc()
        return response
    except Exception as ex:
        LOG.exception(ex)
        failure_counter.labels(HTTP_METHOD_POST, TOKENS_CREATE_URL).inc()
        return cors_response(status=INTERNAL_SERVER_ERROR,
                             xerror=str(ex),
                             body=str(ex))
Пример #5
0
def tokens_revoke_post(body):  # noqa: E501
    """Revoke a refresh token for an user

    Request to revoke a refresh token for an user  # noqa: E501

    :param body:
    :type body: dict | bytes

    :rtype: Success
    """
    received_counter.labels(HTTP_METHOD_POST, TOKENS_REVOKE_URL).inc()
    if connexion.request.is_json:
        body = Request.from_dict(connexion.request.get_json())  # noqa: E501
    try:
        credmgr = OAuthCredmgr()
        credmgr.revoke_token(refresh_token=body.refresh_token)
        success_counter.labels(HTTP_METHOD_POST, TOKENS_REVOKE_URL).inc()
    except Exception as ex:
        LOG.exception(ex)
        failure_counter.labels(HTTP_METHOD_POST, TOKENS_REVOKE_URL).inc()
        msg = str(ex).replace("\n", "")
        return cors_response(status=INTERNAL_SERVER_ERROR,
                             xerror=msg,
                             body=msg)
    return {}
def certs_get():  # noqa: E501
    """Return Public Keys to verify signature of the tokens

    Json Web Keys # noqa: E501


    :rtype: List[Jwk]
    """
    received_counter.labels(HTTP_METHOD_GET, CERTS_URL).inc()
    try:
        response = Jwks.from_dict(fabric_jwks)
        LOG.debug(response)
        success_counter.labels(HTTP_METHOD_GET, CERTS_URL).inc()
        return response
    except Exception as ex:
        LOG.exception(ex)
        failure_counter.labels(HTTP_METHOD_GET, CERTS_URL).inc()
        return str(ex), 500
    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
    def __init__(self, id_token, idp_claims: dict, project="all", scope="all", cookie: str = None):
        """
        Constructor
        :param id_token: CI Logon Identity Token
        :param idp_claims: CI Logon Identity Claims
        :param project: Project for which token is requested
        :param scope: Scope for which token is requested
        :param cookie: Vouch Proxy Cookie

        :raises Exception in case of error
        """
        if id_token is None or project is None or scope is None:
            raise TokenError("Missing required parameters id_token or project or scope")

        LOG.debug("id_token %s", id_token)
        self.id_token = id_token
        self.claims = idp_claims
        self.project = project
        self.scope = scope
        self.cookie = cookie
        self.encoded = False
        self.token = None
        self.unset = True
Пример #9
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 _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
    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
Пример #12
0
    def get_active_projects_and_roles_from_ldap(self, eppn: str, email: str):
        """
        Return active projects for a user identified by eppn or email
        @params eppn: eppn
        @params email: user email
        @return list of active projects for user
        """
        if eppn:
            ldap_search_filter = '(eduPersonPrincipalName=' + eppn + ')'
        else:
            ldap_search_filter = '(mail=' + email + ')'
        LOG.debug("ldap_host:%s", self.ldap_host)
        LOG.debug("ldap_user:%s", self.ldap_user)
        LOG.debug("ldap_password:%s", self.ldap_password)
        LOG.debug("ldap_search_base:%s", self.ldap_search_base)
        LOG.debug("ldap_search_filter:%s", ldap_search_filter)
        try:
            self.lock.acquire()
            conn = Connection(self.server,
                              self.ldap_user,
                              self.ldap_password,
                              auto_bind=True)
            profile_found = conn.search(self.ldap_search_base,
                                        ldap_search_filter,
                                        attributes=[
                                            'isMemberOf',
                                        ])
            if profile_found:
                attributes = conn.entries[0]['isMemberOf']
                attributes = [attr for attr in attributes if 'active' in attr]
            else:
                attributes = None
            conn.unbind()
        finally:
            self.lock.release()
        LOG.debug(attributes)
        projects = None
        roles = None
        if attributes is not None:
            projects = {}
            roles = []
            for a in attributes:
                m = re.match('CO:COU:(.+?):members:active', a)
                if m:
                    found = m.group(1)
                    if found not in self.project_ignore_list:
                        if found in self.roles_list or "-po" in found or "-pm" in found:
                            roles.append(found)
                        else:
                            projects[found] = []

        LOG.debug("Projects: %s, Roles: %s", projects, roles)
        return roles, projects
Пример #13
0
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")
if code != ValidateCode.VALID:
    LOG.error("Failed to encode JWK")
    raise jwk_public_key_rsa