Example #1
0
def validate_claim(
    token_info: Claim,
    dataset_id: DatasetId,
    permissions: List[DatasetPermission],
    organization_id: Optional[OrganizationId] = None,
) -> AuthContext:

    # API v1 does not specify an organization. Additionally, for v1 API
    # compatability, we assume the organization of context is the head
    # organization in the given JWT claim:
    if organization_id is None:
        if token_info.head_organization_node_id is None:
            raise OAuthProblem("Missing organization node id")
        organization_int_id: OrganizationId = OrganizationId(
            token_info.head_organization_id.id
        )
    else:
        organization_int_id = OrganizationId(organization_id)

    dataset_int_id = dataset_id

    if token_info.is_user_claim:
        if token_info.content.node_id is None:
            raise OAuthProblem("Missing user node ID")
        user_node_id = UserNodeId(token_info.content.node_id)
    else:
        user_node_id = SERVICE_USER_NODE_ID

    auth_organization_id = RoleOrganizationId(organization_int_id)
    auth_dataset_id = RoleDatasetId(dataset_int_id)

    if not token_info.has_organization_access(auth_organization_id):
        raise Forbidden

    for permission in permissions:
        if not token_info.has_dataset_access(auth_dataset_id, permission):
            raise Forbidden

    # (invariant):
    # These roles should never be None and are assumed to be valid given the
    # checks above.
    organization_role = token_info.get_role(auth_organization_id)
    dataset_role = token_info.get_role(auth_dataset_id)

    def get_locked():
        for role in token_info.content.roles:
            # we do not pass through the locked field for a wildcard role,
            # since by definition the wildcard means we don't know individual datasets are locked or not
            if role.id == auth_dataset_id and isinstance(role, DatasetRole):
                return role.locked
        return None

    return AuthContext(
        organization_id=organization_int_id,
        dataset_id=dataset_id,
        user_node_id=user_node_id,
        organization_node_id=organization_role.node_id,
        dataset_node_id=dataset_role.node_id,
        locked=get_locked(),
    )
Example #2
0
def test_permission_requires_an_organization_role_specifier(
        app_context, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                DatasetRole(id=dataset_id,
                            node_id=dataset_node_id,
                            role=RoleType.OWNER)
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    with pytest.raises(Forbidden):
        sample_update_route(
            dataset_id=str(dataset_id.id),
            token_info=claim,
            organization_id=str(organization_id.id),
            body={"k": 1},
        )
Example #3
0
def test_permission_required_to_a_access_specific_dataset(
        app_context, valid_organization, valid_dataset, other_valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id_1, _ = valid_dataset
    dataset_id_2, dataset_node_id_2 = other_valid_dataset

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id=dataset_id_2,
                            node_id=dataset_node_id_2,
                            role=RoleType.OWNER),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    with pytest.raises(Forbidden):
        sample_update_route(
            dataset_id=str(dataset_id_1.id),
            token_info=claim,
            organization_id=str(organization_id.id),
            body={"k": 1},
        )
Example #4
0
def test_authorization_resolves_dataset_id_from_api_with_wildcard_claim(
        app_context, api_client, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    api_client.get_dataset_response = api.Dataset(id=dataset_node_id,
                                                  int_id=dataset_id.id,
                                                  name="foo")

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id=DatasetId("*"), role=RoleType.EDITOR),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    sample_view_route(
        dataset_id=dataset_node_id,
        token_info=claim,
        organization_id=str(organization_id.id),
        body={"k": 1},
    )(organization_id.id, dataset_id.id)
Example #5
0
def test_authorization_rejects_nonexistent_dataset_integer_id(
        valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id=dataset_id,
                            node_id=dataset_node_id,
                            role=RoleType.OWNER),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    with pytest.raises(Forbidden):
        sample_update_route(
            dataset_id=9999,
            token_info=claim,
            organization_id=str(organization_id.id),
            body={"k": 1},
        )(organization_id.id, dataset_id.id)
Example #6
0
def test_permission_required_raises_forbidden_when_dataset_role_is_too_low(
        app_context, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id=dataset_id,
                            node_id=dataset_node_id,
                            role=RoleType.VIEWER),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    # sample_route requires EDITOR permissions, which are higher than VIEWER:
    with pytest.raises(Forbidden):
        sample_update_route(
            dataset_id=str(dataset_id.id),
            token_info=claim,
            organization_id=str(organization_id.id),
            body={"k": 1},
        )
Example #7
0
def test_authorization_allows_updates_with_locked_false_claim(
        app_context, api_client, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    api_client.get_dataset_response = api.Dataset(id=dataset_node_id,
                                                  int_id=dataset_id.id,
                                                  name="foo")

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id=dataset_id, role=RoleType.EDITOR, locked=False),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    sample_update_route(
        dataset_id=str(dataset_id.id),
        token_info=claim,
        organization_id=str(organization_id.id),
        body={"k": 1},
    )
Example #8
0
def test_authorization_requires_integer_organization_id(
        app_context, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id=dataset_id,
                            node_id=dataset_node_id,
                            role=RoleType.OWNER),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    with pytest.raises(OAuthProblem):
        sample_update_route(
            dataset_id=str(dataset_id.id),
            token_info=claim,
            organization_id=str(organization_node_id),
            body={"k": 1},
        )(organization_id.id, dataset_id.id)
Example #9
0
def authorized_service_token(
    config, valid_organization, valid_dataset, other_valid_dataset
):
    organization_id, organization_node_id = valid_organization
    dataset_id_1, dataset_node_id_1 = valid_dataset
    dataset_id_2, dataset_node_id_2 = other_valid_dataset

    data = ServiceClaim(
        roles=[
            OrganizationRole(
                id=organization_id, node_id=organization_node_id, role=RoleType.OWNER
            ),
            DatasetRole(
                id=dataset_id_1,
                node_id=dataset_node_id_1,
                role=RoleType.OWNER,
                locked=False,
            ),
            DatasetRole(
                id=dataset_id_2,
                node_id=dataset_node_id_2,
                role=RoleType.OWNER,
                locked=False,
            ),
        ]
    )

    claim = Claim.from_claim_type(data, seconds=JWT_EXPIRATION_SECS)
    return to_utf8(claim.encode(config.jwt_config))
Example #10
0
def test_permission_required_decorator(app_context, valid_organization,
                                       valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(
                    id=dataset_id,
                    node_id=dataset_node_id,
                    role=RoleType.OWNER,
                    locked=False,
                ),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    sample_update_route(
        dataset_id=str(dataset_id.id),
        token_info=claim,
        organization_id=str(organization_id.id),
        body={"k": 1},
    )(organization_id.id, dataset_id.id)
Example #11
0
def service_claim(organization_id, dataset_id, jwt_config: JwtConfig) -> str:
    data = ServiceClaim(roles=[
        OrganizationRole(id=OrganizationId(organization_id),
                         role=RoleType.OWNER),
        DatasetRole(id=DatasetId(dataset_id), role=RoleType.OWNER),
    ])
    claim = Claim.from_claim_type(data, seconds=30)
    return to_utf8(claim.encode(jwt_config))
Example #12
0
def unauthorized_user_token(jwt_config, invalid_organization, valid_dataset):
    organization_id, organization_node_id = invalid_organization
    dataset_id, dataset_node_id = valid_dataset
    data = UserClaim(
        id=12345,
        roles=[
            OrganizationRole(
                id=organization_id, node_id=organization_node_id, role=RoleType.OWNER
            ),
            DatasetRole(id=dataset_id, node_id=dataset_node_id, role=RoleType.OWNER),
        ],
    )
    claim = Claim.from_claim_type(data, seconds=JWT_EXPIRATION_SECS)
    return to_utf8(claim.encode(jwt_config))
Example #13
0
def expired_user_token(jwt_config, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset
    data = UserClaim(
        id=12345,
        roles=[
            OrganizationRole(
                id=organization_id, node_id=organization_node_id, role=RoleType.OWNER
            ),
            DatasetRole(id=dataset_id, node_id=dataset_node_id, role=RoleType.OWNER),
        ],
    )
    claim = Claim.from_claim_type(data, -1)
    return to_utf8(claim.encode(jwt_config))
Example #14
0
def organization_token(config, valid_organization, other_valid_dataset, valid_user):
    organization_id, organization_node_id = valid_organization
    user_id, user_node_id = valid_user

    data = UserClaim(
        id=user_id,
        node_id=user_node_id,
        roles=[
            OrganizationRole(
                id=organization_id, node_id=organization_node_id, role=RoleType.OWNER
            )
        ],
    )
    claim = Claim.from_claim_type(data, seconds=JWT_EXPIRATION_SECS)
    return to_utf8(claim.encode(config.jwt_config))
Example #15
0
def authorize_search(organization_id: int, trace_id: TraceId,
                     token_info: Claim):

    if not token_info.is_user_claim:
        raise OAuthProblem("Requires a user claim")

    if not token_info.has_organization_access(
            RoleOrganizationId(organization_id)):
        raise Forbidden

    user_id = UserId(token_info.content.node_id)

    datasets = PennsieveApiClient.get().get_datasets(
        headers=dict(**auth_header(), **with_trace_id_header(trace_id)))

    return SearchDatabase(
        db=current_app.config["db"],
        organization_id=organization_id,
        user_id=user_id,
        datasets=datasets,
    )
Example #16
0
def test_authorization_rejects_nonexistent_dataset_node_ids_with_wildcard_claim(
        app_context, api_client, valid_organization, valid_dataset):
    organization_id, organization_node_id = valid_organization
    dataset_id, dataset_node_id = valid_dataset

    api_client.raise_exception(
        ExternalRequestError(
            status_code=404,
            method="GET",
            url="/datasets/N:dataset:does-not-exist",
            content="Dataset does not exist",
        ))

    claim = Claim.from_claim_type(
        UserClaim(
            id=DEFAULT_USER_ID,
            node_id=DEFAULT_USER_NODE_ID,
            roles=[
                OrganizationRole(
                    id=organization_id,
                    node_id=organization_node_id,
                    role=RoleType.OWNER,
                ),
                DatasetRole(id="*", role=RoleType.EDITOR),
            ],
        ),
        TOKEN_EXPIRATION_S,
    )

    with pytest.raises(OAuthProblem, match="Dataset does not exist"):
        sample_update_route(
            dataset_id="N:dataset:does-not-exist",
            token_info=claim,
            organization_id=str(organization_id.id),
            body={"k": 1},
        )(organization_id.id, dataset_id.id)
Example #17
0
def decode_token(token: str) -> Claim:
    """
    Decode a JWT into a claim.

    Raises
    ------
    - connexion.exceptions.OAuthProblem
    - jwt.exceptions.ExpiredSignatureError
    """
    try:
        claim = Claim.from_token(token, Config.from_app().jwt_config)
    except DecodeError:
        raise connexion.exceptions.OAuthProblem(description="Invalid token")

    # This is a hack to work around Connexion internals.  The security wrapper
    # expects the claim to be a dictionary, but we have already built the
    # `Claim` object. The wrapper is trying to extract a user, which we don't
    # need so stub out `get` to always return `None`
    #
    # Ref: https://github.com/zalando/connexion/blob/08e4536e5e6c284aaabcfb6fa159c738dbae7758/connexion/decorators/security.py#L297

    claim.get = lambda x, y=None: None

    return claim