Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions policy/token/restriction/show.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package identity.token_restriction_show

import data.identity

# Create mapping.

default allow := false

allow if {
"admin" in input.credentials.roles
}

allow if {
"manager" in input.credentials.roles
}

violation contains {"field": "domain_id", "msg": "showing token restrictions requires `admin` role."} if {
#identity.foreign_mapping
not "admin" in input.credentials.roles
}

#violation contains {"field": "role", "msg": "creating global mapping requires `admin` role."} if {
# identity.global_mapping
# not "admin" in input.credentials.roles
#}
#
#violation contains {"field": "role", "msg": "creating mapping requires `manager` role."} if {
# identity.own_mapping
# not "member" in input.credentials.roles
#}
15 changes: 15 additions & 0 deletions policy/token/restriction/show_test.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package test_token_restriction_show

import data.identity.token_restriction_show

test_allowed if {
token_restriction_show.allow with input as {"credentials": {"roles": ["admin"]}}
#token_restriction_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "domain"}}
#token_restriction_show.allow with input as {"credentials": {"roles": ["reader"]}, "target": {"domain_id": null}}
}

test_forbidden if {
not token_restriction_show.allow with input as {"credentials": {"roles": []}}
not token_restriction_show.allow with input as {"credentials": {"roles": ["reader"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}}
not token_restriction_show.allow with input as {"credentials": {"roles": ["member"], "domain_id": "domain"}, "target": {"domain_id": "other_domain"}}
}
4 changes: 3 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ use crate::api::types::*;
modifiers(&SecurityAddon),
tags(
(name="identity_providers", description=v4::federation::identity_provider::DESCRIPTION),
(name="mappings", description=v4::federation::mapping::DESCRIPTION)
(name="mappings", description=v4::federation::mapping::DESCRIPTION),
(name="token", description=v4::token::DESCRIPTION),
(name="token_restrictions", description=v4::token::restriction::DESCRIPTION),
)
)]
pub struct ApiDoc;
Expand Down
2 changes: 2 additions & 0 deletions src/api/v4/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod federation;
pub mod group;
pub mod role;
pub mod role_assignment;
pub mod token;
pub mod user;

use crate::api::types::*;
Expand All @@ -40,6 +41,7 @@ pub(super) fn openapi_router() -> OpenApiRouter<ServiceState> {
.nest("/federation", federation::openapi_router())
.nest("/role_assignments", role_assignment::openapi_router())
.nest("/roles", role::openapi_router())
.nest("/tokens", token::openapi_router())
.nest("/users", user::openapi_router())
.routes(routes!(version))
}
Expand Down
28 changes: 28 additions & 0 deletions src/api/v4/token/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

use utoipa_axum::router::OpenApiRouter;

use crate::keystone::ServiceState;

pub mod restriction;
pub mod types;

pub(crate) static DESCRIPTION: &str = r#"Token API.

"#;

pub(super) fn openapi_router() -> OpenApiRouter<ServiceState> {
OpenApiRouter::new().nest("/restrictions", restriction::openapi_router())
}
119 changes: 119 additions & 0 deletions src/api/v4/token/restriction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Token restrictions API
use utoipa_axum::{router::OpenApiRouter, routes};

use crate::keystone::ServiceState;

pub(crate) static DESCRIPTION: &str = r#"Token restrictions API.

Token restrictions allow controlling multiple aspects of the authentication and authorization.

- `allow_rescope` controls whether it is allowed to change the scope of the token. That is by default possible for normal (i.e. password) authentication, is forbidden for the application credentials and may need to be also forbidden for the JWT based authentication.

- `allow_renew` controls whether it is possible to renew the token (get a new token from existing token). This is most likely undisired for the JWT auth.

- `project_id` may control that this token can be only issued for the fixed project scope.

- `user_id` may specify the fixed user_id that will be used when issuing the token independently of the authentication. This is useful for Service Accounts.

- `roles` binds the roles of the issued token on the scope. Using this bypasses necessity to grant the roles explicitly to the user.
"#;

mod show;

pub(super) fn openapi_router() -> OpenApiRouter<ServiceState> {
OpenApiRouter::new().routes(routes!(show::show))
}

#[cfg(test)]
mod tests {

use sea_orm::DatabaseConnection;
use std::sync::Arc;

use crate::config::Config;
use crate::federation::MockFederationProvider;
use crate::identity::types::UserResponse;
use crate::keystone::{Service, ServiceState};
use crate::policy::{MockPolicy, MockPolicyFactory, PolicyError, PolicyEvaluationResult};
use crate::provider::Provider;
use crate::token::{MockTokenProvider, Token, UnscopedPayload};

pub(crate) fn get_mocked_state(
mut token_mock: MockTokenProvider,
policy_allowed: bool,
policy_allowed_see_other_domains: Option<bool>,
) -> ServiceState {
token_mock.expect_validate_token().returning(|_, _, _| {
Ok(Token::Unscoped(UnscopedPayload {
user_id: "bar".into(),
..Default::default()
}))
});
token_mock
.expect_expand_token_information()
.returning(|_, _, _| {
Ok(Token::Unscoped(UnscopedPayload {
user_id: "bar".into(),
user: Some(UserResponse {
id: "bar".into(),
domain_id: "udid".into(),
..Default::default()
}),
..Default::default()
}))
});

let provider = Provider::mocked_builder()
.token(token_mock)
.build()
.unwrap();

let mut policy_factory_mock = MockPolicyFactory::default();
if policy_allowed {
policy_factory_mock.expect_instantiate().returning(move || {
let mut policy_mock = MockPolicy::default();
if policy_allowed_see_other_domains.is_some_and(|x| x) {
policy_mock
.expect_enforce()
.returning(|_, _, _, _| Ok(PolicyEvaluationResult::allowed_admin()));
} else {
policy_mock
.expect_enforce()
.returning(|_, _, _, _| Ok(PolicyEvaluationResult::allowed()));
}
Ok(policy_mock)
});
} else {
policy_factory_mock.expect_instantiate().returning(|| {
let mut policy_mock = MockPolicy::default();
policy_mock.expect_enforce().returning(|_, _, _, _| {
Err(PolicyError::Forbidden(PolicyEvaluationResult::forbidden()))
});
Ok(policy_mock)
});
}
Arc::new(
Service::new(
Config::default(),
DatabaseConnection::Disconnected,
provider,
policy_factory_mock,
)
.unwrap(),
)
}
}
Loading
Loading