From 5f7d1963be2139fe94eda17782b30b5329c68c34 Mon Sep 17 00:00:00 2001 From: Caleb Hill Date: Mon, 13 Jun 2022 09:45:10 -0500 Subject: [PATCH 1/5] Feature guard new_unchecked fn for diesel This commit adds a feature guard around the new_unchecked fn in the rbac assignment store. Signed-off-by: Caleb Hill --- libsplinter/src/rbac/store/assignment/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libsplinter/src/rbac/store/assignment/mod.rs b/libsplinter/src/rbac/store/assignment/mod.rs index dd3ab96c95..d730826a7d 100644 --- a/libsplinter/src/rbac/store/assignment/mod.rs +++ b/libsplinter/src/rbac/store/assignment/mod.rs @@ -50,6 +50,7 @@ impl Assignment { (self.identity, self.roles) } + #[cfg(feature = "diesel")] pub(super) fn new_unchecked(identity: Identity, roles: Vec) -> Self { Self { identity, roles } } From db3457d87e08dfd3282a50f2ae5e8e5e5e1bf92b Mon Sep 17 00:00:00 2001 From: Caleb Hill Date: Fri, 29 Jul 2022 10:43:18 -0500 Subject: [PATCH 2/5] Make OAuthClient constructor public OAuth integration tests make heavy use of the OAuthClient struct as you might expect. Those integration tests are moving to the rest_api crates because they also use actix-web specific code. This presents a problem in that there wasn't a way to construct a client outside of libsplinter. This commit makes the OAuthClient constructor public so it can be used in the actix_web_1 crate. Signed-off-by: Caleb Hill --- libsplinter/src/oauth/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsplinter/src/oauth/mod.rs b/libsplinter/src/oauth/mod.rs index a560767a6d..a18544501e 100644 --- a/libsplinter/src/oauth/mod.rs +++ b/libsplinter/src/oauth/mod.rs @@ -76,7 +76,7 @@ impl OAuthClient { /// provider. /// * `profile_provider` - The OAuth profile provider used to retrieve the profile /// information of the authenticated user from the OAuth provider. - fn new( + pub fn new( client: BasicClient, extra_auth_params: Vec<(String, String)>, scopes: Vec, From 19b499c449ff196f4beaf6fdaf5dddbf7727f21b Mon Sep 17 00:00:00 2001 From: Caleb Hill Date: Fri, 29 Jul 2022 10:43:48 -0500 Subject: [PATCH 3/5] Add impl for PendingAuthorization This commit adds a constructor and a getter used by some tests in the splinter_rest_api_actix_web_1 crate. Signed-off-by: Caleb Hill --- libsplinter/src/oauth/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libsplinter/src/oauth/mod.rs b/libsplinter/src/oauth/mod.rs index a18544501e..505562852a 100644 --- a/libsplinter/src/oauth/mod.rs +++ b/libsplinter/src/oauth/mod.rs @@ -244,6 +244,19 @@ pub struct PendingAuthorization { client_redirect_url: String, } +impl PendingAuthorization { + pub fn get_client_redirect_url(&self) -> &str { + &self.client_redirect_url + } + + pub fn new(pkce_verifier: String, client_redirect_url: String) -> Self { + Self { + pkce_verifier, + client_redirect_url, + } + } +} + /// User information returned by the OAuth2 client pub struct UserInfo { /// The access token to be used for authentication in future requests From 7a98b241ebec50ddd706a14ecb6dda1c787f4ec4 Mon Sep 17 00:00:00 2001 From: Caleb Hill Date: Thu, 26 May 2022 14:54:49 -0500 Subject: [PATCH 4/5] Add deps to common This commit adds the log,rand, and jsonwebtoken dependencies to common. Those crates are used by code moving into this crate. Signed-off-by: Caleb Hill --- rest_api/actix_web_1/Cargo.toml | 4 ++++ rest_api/common/Cargo.toml | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/rest_api/actix_web_1/Cargo.toml b/rest_api/actix_web_1/Cargo.toml index 16527ea05e..dc5931ae7f 100644 --- a/rest_api/actix_web_1/Cargo.toml +++ b/rest_api/actix_web_1/Cargo.toml @@ -48,7 +48,9 @@ stable = [ "admin-service", "authorization", "biome", + "biome-credentials", "biome-key-management", + "oauth", "registry", "rest-api", "scabbard-service", @@ -69,8 +71,10 @@ admin-service = [ ] authorization = ["splinter/authorization", "splinter-rest-api-common/authorization"] biome = ["splinter/biome", "serde"] +biome-credentials = ["biome"] biome-key-management = ["biome", "splinter/biome-key-management"] registry = ["splinter/registry"] +oauth = ["log"] rest-api = ["splinter/rest-api"] scabbard-service = ["scabbard/splinter-service", "scabbard/rest-api", "transact", "log"] service = ["splinter/runtime-service", "serde_json", "log"] diff --git a/rest_api/common/Cargo.toml b/rest_api/common/Cargo.toml index e1ea8cf644..c0f07e88f0 100644 --- a/rest_api/common/Cargo.toml +++ b/rest_api/common/Cargo.toml @@ -23,6 +23,9 @@ description = """\ """ [dependencies] +log = "0.4" +rand = "0.8" +jsonwebtoken = { version = "7.0" } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", optional = true } splinter = { path = "../../libsplinter" } @@ -38,6 +41,8 @@ stable = [ "default", # The following features are stable: "authorization", + "oauth", + "rbac" ] experimental = [ @@ -47,5 +52,7 @@ experimental = [ ] authorization = ["splinter/authorization"] +oauth = ["splinter/oauth"] +rbac = ["splinter/authorization-handler-rbac"] scabbard-service = ["scabbard", "splinter/rest-api", "splinter/rest-api-actix-web-1", "serde_json"] service-endpoint = [] From 052c7171a31066fdb0d5d57559cbdf8a6e8bbc57 Mon Sep 17 00:00:00 2001 From: Caleb Hill Date: Fri, 29 Jul 2022 10:24:08 -0500 Subject: [PATCH 5/5] Move all rest-api code out of libsplinter This commit moves all actix-web and supporting rest-api code into the actix-web-1 and common crates. The main discriminant is 'does this have actix-web-1 stuff in it' => actix-web-1 else common. Below is an accounting of where things moved from libsplinter/src/rest_api. This commit encompasses these moves as well as updating everything in the rest_api and splinterd crates to import structs from the right places. 1 auth ====== 1.1 identity => rest_api/common/auth/identity ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.1.1 biome.rs -------------- 1.1.2 cylinder.rs ----------------- 1.1.3 mod.rs ------------ 1.1.4 oauth.rs -------------- 1.2 mod.rs => rest_api/common/auth/{authorization_result, authorization_header, bearer_token}.mod.rs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.3 actix.rs => rest_api/actix_web_1/auth/{transform,middleware}.rs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1.4 authorization ~~~~~~~~~~~~~~~~~ 1.4.1 permission_map.rs => Split between common/auth/permission_map.rs and actix_web_1/auth/method.rs ------------------------------------------------------------------------------------------------------ 1.4.2 rbac ---------- * 1.4.2.1 handler.rs => common/auth/rbac/handler.rs * 1.4.2.2 mod.rs * 1.4.2.3 rest_api + 1.4.2.3.1 resources => common/auth/rbac/resources - 1.4.2.3.1.1 roles.rs - 1.4.2.3.1.2 mod.rs - 1.4.2.3.1.3 assignments.rs + 1.4.2.3.2 mod.rs => delete + 1.4.2.3.3 actix_web_1 => actix_web_1/auth/rbac - 1.4.2.3.3.1 error.rs => common/auth/rbac/error.rs - 1.4.2.3.3.2 roles.rs - 1.4.2.3.3.3 mod.rs - 1.4.2.3.3.4 assignments.rs 1.4.3 mod.rs => common/auth/{permission,authorization_handler_result,authorization_handler}.rs ---------------------------------------------------------------------------------------------- * 1.4.3.1 Split * 1.4.3.2 Move 1.4.4 maintenance ----------------- * 1.4.4.1 mod.rs => split common/auth/maintenance/{maintenance_mode_authorization_handler,mod}.rs + 1.4.4.1.1 Split + 1.4.4.1.2 Move * 1.4.4.2 routes + 1.4.4.2.1 actix.rs => actix-web-1/auth/maintenance/routes.rs + 1.4.4.2.2 mod.rs => actix-web-1/auth/maintenance/mod.rs + 1.4.4.2.3 resources.rs => common/auth/maintenance/ 1.4.5 allow_keys.rs => common/auth/allow_keys.rs ------------------------------------------------ 1.4.6 routes ------------ * 1.4.6.1 mod.rs => actix-web/actix-web-1/routes/resource_provider.rs * 1.4.6.2 actix.rs => actix-web/actix-web/routes/make_permissions_resource.rs * 1.4.6.3 resources.rs => common/auth/routes?/resources.rs 2 secrets => common/auth/secrets ================================ 2.1 error.rs ~~~~~~~~~~~~ 2.2 mod.rs ~~~~~~~~~~ 2.3 auto_secret_manager.rs ~~~~~~~~~~~~~~~~~~~~~~~~~~ 3 cors.rs => actix-web-1/auth/cors.rs ===================================== 4 response_models.rs => common/response_models.rs ================================================= 5 paging.rs => delete, already exists in common =============================================== 6 mod.rs => Split common/{OauthConfig,bind_config,mod}.rs ========================================================= 7 sessions => common/sessions/ ============================== 7.1 claims.rs ~~~~~~~~~~~~~ 7.2 token_issuer.rs ~~~~~~~~~~~~~~~~~~~ 7.3 error.rs ~~~~~~~~~~~~ 7.4 mod.rs ~~~~~~~~~~ 8 errors.rs => common/auth/error.rs =================================== Signed-off-by: Caleb Hill --- libsplinter/Cargo.toml | 10 +- .../src/admin/service/messages/v1/mod.rs | 1 + libsplinter/src/biome/credentials/mod.rs | 2 - .../src/biome/credentials/rest_api/mod.rs | 37 - libsplinter/src/biome/profile/mod.rs | 2 - libsplinter/src/biome/profile/rest_api/mod.rs | 7 - libsplinter/src/lib.rs | 10 +- libsplinter/src/oauth/builder/openid.rs | 1 + libsplinter/src/oauth/mod.rs | 2 - libsplinter/src/oauth/profile/openid.rs | 207 ---- libsplinter/src/oauth/subject/github.rs | 1 + libsplinter/src/oauth/subject/openid.rs | 1 + libsplinter/src/rbac/store/identity.rs | 15 - libsplinter/src/registry/yaml/remote.rs | 924 ----------------- .../authorization/permission_map/method.rs | 44 - .../auth/authorization/rbac/rest_api/mod.rs | 38 - libsplinter/src/rest_api/mod.rs | 116 +-- libsplinter/src/rest_api/paging.rs | 209 ---- .../src/service/instance/factory/mod.rs | 4 - libsplinter/src/service/instance/mod.rs | 2 - libsplinter/src/service/mod.rs | 2 - rest_api/actix_web_1/Cargo.toml | 48 +- rest_api/actix_web_1/src/admin/circuits.rs | 63 +- .../src/admin/circuits_circuit_id.rs | 19 +- rest_api/actix_web_1/src/admin/mod.rs | 8 +- rest_api/actix_web_1/src/admin/proposals.rs | 54 +- .../src/admin/proposals_circuit_id.rs | 19 +- .../src/admin/resources/v1/circuits.rs | 2 +- .../src/admin/resources/v1/proposals.rs | 2 +- .../src/admin/resources/v2/circuits.rs | 2 +- .../src/admin/resources/v2/proposals.rs | 2 +- rest_api/actix_web_1/src/admin/submit.rs | 3 +- .../actix_web_1/src/admin/ws_register_type.rs | 11 +- .../src/auth/maintenance}/actix.rs | 22 +- .../actix_web_1/src/auth/maintenance}/mod.rs | 29 +- .../src/auth/make_permission_resource.rs | 14 +- .../actix_web_1/src/auth}/middleware.rs | 12 +- .../actix_web_1/src/auth}/mod.rs | 54 +- .../actix_web_1/src/auth/oauth}/callback.rs | 134 ++- .../actix_web_1/src/auth/oauth}/list_users.rs | 66 +- .../actix_web_1/src/auth/oauth}/login.rs | 90 +- .../actix_web_1/src/auth/oauth}/logout.rs | 20 +- .../actix_web_1/src/auth/oauth/mod.rs | 383 +++---- .../src/auth/oauth}/resource_provider.rs | 18 +- .../actix_web_1/src/auth/rbac}/assignments.rs | 79 +- .../actix_web_1/src/auth/rbac}/mod.rs | 6 +- .../actix_web_1/src/auth/rbac}/roles.rs | 69 +- .../actix_web_1/src/auth/resource_provider.rs | 31 +- .../actix_web_1/src/auth}/transform.rs | 6 +- .../src/biome/credentials}/authorize.rs | 8 +- .../src/biome/credentials}/config.rs | 4 +- .../src/biome/credentials}/login.rs | 20 +- .../src/biome/credentials}/logout.rs | 18 +- .../actix_web_1/src/biome/credentials}/mod.rs | 38 +- .../src/biome/credentials}/register.rs | 17 +- .../biome/credentials}/resources/authorize.rs | 4 +- .../credentials}/resources/credentials.rs | 4 +- .../credentials}/resources/key_management.rs | 6 +- .../src/biome/credentials}/resources/mod.rs | 0 .../src/biome/credentials}/resources/token.rs | 0 .../src/biome/credentials}/resources/user.rs | 2 +- .../src/biome/credentials}/token.rs | 35 +- .../src/biome/credentials}/user.rs | 49 +- .../src/biome/credentials}/verify.rs | 21 +- .../src/biome/key_management/endpoints.rs | 11 +- .../src/biome/key_management/mod.rs | 23 +- rest_api/actix_web_1/src/biome/mod.rs | 4 + .../actix_web_1/src/biome/profile}/mod.rs | 18 +- .../src/biome/profile}/profiles.rs | 12 +- .../src/biome/profile}/profiles_identity.rs | 13 +- .../actix_web_1/src/biome/profile/route.rs | 12 +- .../actix_web_1/src}/cors.rs | 0 .../actix_web_1/src/endpoint_factory.rs | 0 .../framework/actix_web_1/resource/builder.rs | 25 + .../actix_web_1/resource/resource_method.rs | 101 ++ .../actix_web_1/src/framework}/api.rs | 17 +- .../actix_web_1/src/framework}/auth.rs | 15 +- .../actix_web_1/src/framework}/builder.rs | 24 +- .../actix_web_1/src/framework}/error.rs | 0 .../actix_web_1/src/framework}/guard.rs | 1 + .../actix_web_1/src/framework}/mod.rs | 8 +- .../actix_web_1/src/framework}/resource.rs | 12 +- .../actix_web_1/src/framework}/websocket.rs | 0 rest_api/actix_web_1/src/lib.rs | 4 + .../src/open_api/resource_provider.rs | 7 +- rest_api/actix_web_1/src/registry/mod.rs | 931 +++++++++++++++++- rest_api/actix_web_1/src/registry/nodes.rs | 63 +- .../src/registry/nodes_identity.rs | 19 +- .../src/registry/resources/nodes.rs | 2 +- .../src/scabbard/batch_statuses.rs | 9 +- rest_api/actix_web_1/src/scabbard/batches.rs | 8 +- rest_api/actix_web_1/src/scabbard/mod.rs | 735 +++++++++++++- rest_api/actix_web_1/src/scabbard/state.rs | 8 +- .../actix_web_1/src/scabbard/state_address.rs | 8 +- .../actix_web_1/src/scabbard/state_root.rs | 8 +- .../actix_web_1/src/scabbard/ws_subscribe.rs | 13 +- rest_api/actix_web_1/src/service/builder.rs | 8 +- .../actix_web_1/src/service/endpoints.rs | 9 +- rest_api/actix_web_1/src/service/mod.rs | 4 +- rest_api/actix_web_1/src/status/mod.rs | 2 +- .../src/status/resource_provider.rs | 8 +- rest_api/common/Cargo.toml | 13 + .../common/src/auth}/allow_keys.rs | 6 +- .../common/src/auth/authorization_handler.rs | 23 +- .../src/auth}/authorization_handler_result.rs | 0 .../common/src}/auth/authorization_header.rs | 27 +- .../common/src}/auth/authorization_result.rs | 2 +- .../common/src}/auth/bearer_token.rs | 38 +- .../common/src}/auth/identity/biome.rs | 4 +- .../common/src}/auth/identity/cylinder.rs | 4 +- .../common/src}/auth/identity/mod.rs | 18 +- rest_api/common/src/auth/identity/oauth.rs | 181 ++++ .../auth/maintenance/authorization_handler.rs | 29 +- .../common/src/auth/maintenance}/mod.rs | 9 +- .../common/src/auth/maintenance}/resources.rs | 2 + .../common/src}/auth/mod.rs | 147 ++- .../common/src/auth/oauth}/mod.rs | 12 +- .../src/auth/oauth}/resources/callback.rs | 2 + .../src/auth/oauth}/resources/list_users.rs | 9 +- .../common/src/auth/oauth}/resources/mod.rs | 3 + .../common/src/auth}/permission.rs | 0 .../common/src/auth/permission_map/method.rs | 14 +- .../common/src/auth}/permission_map/mod.rs | 29 +- .../auth}/permission_map/path_component.rs | 0 .../permission_map/request_definition.rs | 0 .../common/src/auth/rbac}/error.rs | 6 +- .../common/src/auth}/rbac/handler.rs | 10 +- rest_api/common/src/auth/rbac/mod.rs | 26 + .../src/auth/rbac}/resources/assignments.rs | 8 +- .../common/src/auth/rbac}/resources/mod.rs | 4 +- .../common/src/auth/rbac}/resources/roles.rs | 8 +- .../common/src/auth}/resources.rs | 2 + .../common/src}/bind_config.rs | 0 .../common/src/error}/mod.rs | 2 + .../common/src/error}/request_error.rs | 0 .../src/{error.rs => error/response_error.rs} | 0 .../src/error}/rest_api_server_error.rs | 4 +- rest_api/common/src/lib.rs | 9 + .../common/src}/oauth_config.rs | 2 +- rest_api/common/src/paging/v1/mod.rs | 4 + .../common/src/percent_encode_filter_query.rs | 34 + .../common/src}/response_models.rs | 2 + rest_api/common/src/scabbard/mod.rs | 2 +- .../src}/secrets/auto_secret_manager.rs | 0 .../common/src}/secrets/error.rs | 0 .../common/src}/secrets/mod.rs | 0 .../common/src}/sessions/claims.rs | 2 + .../common/src}/sessions/error.rs | 2 +- .../common/src}/sessions/mod.rs | 4 +- .../common/src}/sessions/token_issuer.rs | 2 +- services/scabbard/libscabbard/Cargo.toml | 2 + .../libscabbard/src/client/reqwest/mod.rs | 21 +- splinterd/Cargo.toml | 30 +- splinterd/src/daemon/error.rs | 2 +- splinterd/src/daemon/mod.rs | 51 +- splinterd/src/node/builder/mod.rs | 18 +- splinterd/src/node/runnable/admin.rs | 2 +- splinterd/src/node/runnable/biome.rs | 15 +- splinterd/src/node/runnable/mod.rs | 20 +- splinterd/src/node/running/admin.rs | 2 +- splinterd/src/node/running/mod.rs | 2 +- 161 files changed, 3378 insertions(+), 2761 deletions(-) delete mode 100644 libsplinter/src/biome/credentials/rest_api/mod.rs delete mode 100644 libsplinter/src/rest_api/auth/authorization/permission_map/method.rs delete mode 100644 libsplinter/src/rest_api/auth/authorization/rbac/rest_api/mod.rs delete mode 100644 libsplinter/src/rest_api/paging.rs rename {libsplinter/src/rest_api/auth/authorization/maintenance/routes => rest_api/actix_web_1/src/auth/maintenance}/actix.rs (94%) rename {libsplinter/src/rest_api/auth/authorization/maintenance/routes => rest_api/actix_web_1/src/auth/maintenance}/mod.rs (64%) rename libsplinter/src/rest_api/auth/authorization/routes/actix.rs => rest_api/actix_web_1/src/auth/make_permission_resource.rs (95%) rename {libsplinter/src/rest_api/auth/actix => rest_api/actix_web_1/src/auth}/middleware.rs (95%) rename {libsplinter/src/rest_api/auth/actix => rest_api/actix_web_1/src/auth}/mod.rs (79%) rename {libsplinter/src/oauth/rest_api/actix => rest_api/actix_web_1/src/auth/oauth}/callback.rs (90%) rename {libsplinter/src/oauth/rest_api/actix => rest_api/actix_web_1/src/auth/oauth}/list_users.rs (88%) rename {libsplinter/src/oauth/rest_api/actix => rest_api/actix_web_1/src/auth/oauth}/login.rs (85%) rename {libsplinter/src/oauth/rest_api/actix => rest_api/actix_web_1/src/auth/oauth}/logout.rs (94%) rename libsplinter/src/rest_api/auth/identity/oauth.rs => rest_api/actix_web_1/src/auth/oauth/mod.rs (75%) rename {libsplinter/src/oauth/rest_api => rest_api/actix_web_1/src/auth/oauth}/resource_provider.rs (85%) rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1 => rest_api/actix_web_1/src/auth/rbac}/assignments.rs (96%) rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1 => rest_api/actix_web_1/src/auth/rbac}/mod.rs (93%) rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1 => rest_api/actix_web_1/src/auth/rbac}/roles.rs (96%) rename libsplinter/src/rest_api/auth/authorization/routes/mod.rs => rest_api/actix_web_1/src/auth/resource_provider.rs (67%) rename {libsplinter/src/rest_api/auth/actix => rest_api/actix_web_1/src/auth}/transform.rs (92%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/authorize.rs (91%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/config.rs (98%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/login.rs (96%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/logout.rs (87%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/mod.rs (96%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/register.rs (95%) rename {libsplinter/src/biome/credentials/rest_api => rest_api/actix_web_1/src/biome/credentials}/resources/authorize.rs (89%) rename {libsplinter/src/biome/credentials/rest_api => rest_api/actix_web_1/src/biome/credentials}/resources/credentials.rs (92%) rename {libsplinter/src/biome/credentials/rest_api => rest_api/actix_web_1/src/biome/credentials}/resources/key_management.rs (92%) rename {libsplinter/src/biome/credentials/rest_api => rest_api/actix_web_1/src/biome/credentials}/resources/mod.rs (100%) rename {libsplinter/src/biome/credentials/rest_api => rest_api/actix_web_1/src/biome/credentials}/resources/token.rs (100%) rename {libsplinter/src/biome/credentials/rest_api => rest_api/actix_web_1/src/biome/credentials}/resources/user.rs (96%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/token.rs (95%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/user.rs (89%) rename {libsplinter/src/biome/credentials/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/credentials}/verify.rs (94%) rename {libsplinter/src/biome/profile/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/profile}/mod.rs (72%) rename {libsplinter/src/biome/profile/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/profile}/profiles.rs (90%) rename {libsplinter/src/biome/profile/rest_api/actix_web_1 => rest_api/actix_web_1/src/biome/profile}/profiles_identity.rs (90%) rename libsplinter/src/biome/profile/rest_api/actix_web_1/profile.rs => rest_api/actix_web_1/src/biome/profile/route.rs (88%) rename {libsplinter/src/rest_api => rest_api/actix_web_1/src}/cors.rs (100%) rename libsplinter/src/service/instance/factory/endpoint.rs => rest_api/actix_web_1/src/endpoint_factory.rs (100%) create mode 100644 rest_api/actix_web_1/src/framework/actix_web_1/resource/builder.rs create mode 100644 rest_api/actix_web_1/src/framework/actix_web_1/resource/resource_method.rs rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/api.rs (96%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/auth.rs (86%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/builder.rs (95%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/error.rs (100%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/guard.rs (99%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/mod.rs (81%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/resource.rs (96%) rename {libsplinter/src/rest_api/actix_web_1 => rest_api/actix_web_1/src/framework}/websocket.rs (100%) rename libsplinter/src/service/rest_api.rs => rest_api/actix_web_1/src/service/endpoints.rs (92%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/allow_keys.rs (99%) rename libsplinter/src/rest_api/auth/authorization/mod.rs => rest_api/common/src/auth/authorization_handler.rs (69%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/authorization_handler_result.rs (100%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/authorization_header.rs (70%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/authorization_result.rs (97%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/bearer_token.rs (70%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/identity/biome.rs (97%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/identity/cylinder.rs (95%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/identity/mod.rs (75%) create mode 100644 rest_api/common/src/auth/identity/oauth.rs rename libsplinter/src/rest_api/auth/authorization/maintenance/mod.rs => rest_api/common/src/auth/maintenance/authorization_handler.rs (94%) rename {libsplinter/src/oauth/rest_api/actix => rest_api/common/src/auth/maintenance}/mod.rs (79%) rename {libsplinter/src/rest_api/auth/authorization/maintenance/routes => rest_api/common/src/auth/maintenance}/resources.rs (96%) rename {libsplinter/src/rest_api => rest_api/common/src}/auth/mod.rs (92%) rename {libsplinter/src/oauth/rest_api => rest_api/common/src/auth/oauth}/mod.rs (76%) rename {libsplinter/src/oauth/rest_api => rest_api/common/src/auth/oauth}/resources/callback.rs (97%) rename {libsplinter/src/oauth/rest_api => rest_api/common/src/auth/oauth}/resources/list_users.rs (85%) rename {libsplinter/src/oauth/rest_api => rest_api/common/src/auth/oauth}/resources/mod.rs (82%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/permission.rs (100%) rename libsplinter/src/rest_api/auth/authorization/rbac/mod.rs => rest_api/common/src/auth/permission_map/method.rs (82%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/permission_map/mod.rs (83%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/permission_map/path_component.rs (100%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/permission_map/request_definition.rs (100%) rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1 => rest_api/common/src/auth/rbac}/error.rs (94%) rename {libsplinter/src/rest_api/auth/authorization => rest_api/common/src/auth}/rbac/handler.rs (97%) create mode 100644 rest_api/common/src/auth/rbac/mod.rs rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api => rest_api/common/src/auth/rbac}/resources/assignments.rs (93%) rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api => rest_api/common/src/auth/rbac}/resources/mod.rs (92%) rename {libsplinter/src/rest_api/auth/authorization/rbac/rest_api => rest_api/common/src/auth/rbac}/resources/roles.rs (92%) rename {libsplinter/src/rest_api/auth/authorization/routes => rest_api/common/src/auth}/resources.rs (97%) rename {libsplinter/src/rest_api => rest_api/common/src}/bind_config.rs (100%) rename {libsplinter/src/rest_api/errors => rest_api/common/src/error}/mod.rs (92%) rename {libsplinter/src/rest_api/errors => rest_api/common/src/error}/request_error.rs (100%) rename rest_api/common/src/{error.rs => error/response_error.rs} (100%) rename {libsplinter/src/rest_api/errors => rest_api/common/src/error}/rest_api_server_error.rs (96%) rename {libsplinter/src/rest_api => rest_api/common/src}/oauth_config.rs (98%) create mode 100644 rest_api/common/src/percent_encode_filter_query.rs rename {libsplinter/src/rest_api => rest_api/common/src}/response_models.rs (98%) rename {libsplinter/src/rest_api => rest_api/common/src}/secrets/auto_secret_manager.rs (100%) rename {libsplinter/src/rest_api => rest_api/common/src}/secrets/error.rs (100%) rename {libsplinter/src/rest_api => rest_api/common/src}/secrets/mod.rs (100%) rename {libsplinter/src/rest_api => rest_api/common/src}/sessions/claims.rs (99%) rename {libsplinter/src/rest_api => rest_api/common/src}/sessions/error.rs (99%) rename {libsplinter/src/rest_api => rest_api/common/src}/sessions/mod.rs (93%) rename {libsplinter/src/rest_api => rest_api/common/src}/sessions/token_issuer.rs (97%) diff --git a/libsplinter/Cargo.toml b/libsplinter/Cargo.toml index 2904f07f1a..f7960a2a3c 100644 --- a/libsplinter/Cargo.toml +++ b/libsplinter/Cargo.toml @@ -27,10 +27,7 @@ description = """\ repository = "https://github.com/cargill/splinter" [dependencies] -actix = { version = "0.8", optional = true, default-features = false } actix-http = { version = "0.2", optional = true, default-features = false } -actix-web = { version = "1.0", optional = true, default-features = false } -actix-web-actors = { version = "1.0", optional = true } awc = { version = "0.2", optional = true, default-features = false } base64 = { version = "0.13", optional = true } bcrypt = {version = "0.10", optional = true} @@ -158,7 +155,7 @@ admin-service-event-subscriber-glob = ["admin-service"] authorization-handler-allow-keys = ["authorization"] authorization-handler-maintenance = ["authorization"] authorization = ["rest-api-actix-web-1"] -authorization-handler-rbac = ["authorization", "store"] +authorization-handler-rbac = ["store"] biome = [] biome-client = ["biome"] biome-client-reqwest = ["biome", "reqwest"] @@ -171,7 +168,7 @@ client-reqwest = ["reqwest"] cylinder-jwt = ["cylinder/jwt", "rest-api"] deferred-send = [] events = ["actix-http", "futures", "hyper", "tokio", "awc"] -https-bind = ["actix-web/ssl"] +https-bind = [] memory = ["sqlite"] node-id-store = ["store"] oauth = ["biome", "base64", "oauth2", "reqwest", "rest-api", "store"] @@ -182,10 +179,7 @@ registry-client-reqwest = ["registry-client", "reqwest", "rest-api"] registry-remote = ["reqwest", "registry"] rest-api = ["jsonwebtoken", "percent-encoding"] rest-api-actix-web-1 = [ - "actix", "actix-http", - "actix-web", - "actix-web-actors", "futures", "rest-api", ] diff --git a/libsplinter/src/admin/service/messages/v1/mod.rs b/libsplinter/src/admin/service/messages/v1/mod.rs index 29110e0b0f..0ef6bb1e0d 100644 --- a/libsplinter/src/admin/service/messages/v1/mod.rs +++ b/libsplinter/src/admin/service/messages/v1/mod.rs @@ -17,6 +17,7 @@ pub mod builder; use std::convert::TryFrom; use protobuf::{self, RepeatedField}; +use serde::{self, Deserialize, Serialize}; use crate::admin::error::MarshallingError; use crate::admin::store; diff --git a/libsplinter/src/biome/credentials/mod.rs b/libsplinter/src/biome/credentials/mod.rs index b9d73d5d93..bdf1f43495 100644 --- a/libsplinter/src/biome/credentials/mod.rs +++ b/libsplinter/src/biome/credentials/mod.rs @@ -15,6 +15,4 @@ //! Defines a basic API to register and authenticate a User using a username and a password. //! Not recommended for use in production. -#[cfg(feature = "rest-api-actix-web-1")] -pub mod rest_api; pub mod store; diff --git a/libsplinter/src/biome/credentials/rest_api/mod.rs b/libsplinter/src/biome/credentials/rest_api/mod.rs deleted file mode 100644 index a8e0fcdce9..0000000000 --- a/libsplinter/src/biome/credentials/rest_api/mod.rs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018-2022 Cargill Incorporated -// -// 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. - -mod actix_web_1; -mod resources; - -#[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; - -pub use actix_web_1::{ - BiomeCredentialsRestConfig, BiomeCredentialsRestConfigBuilder, - BiomeCredentialsRestResourceProvider, BiomeCredentialsRestResourceProviderBuilder, -}; - -#[cfg(feature = "authorization")] -const BIOME_USER_READ_PERMISSION: Permission = Permission::Check { - permission_id: "biome.user.read", - permission_display_name: "Biome user read", - permission_description: "Allows the client to view all Biome users", -}; -#[cfg(feature = "authorization")] -const BIOME_USER_WRITE_PERMISSION: Permission = Permission::Check { - permission_id: "biome.user.write", - permission_display_name: "Biome user write", - permission_description: "Allows the client to modify all Biome users", -}; diff --git a/libsplinter/src/biome/profile/mod.rs b/libsplinter/src/biome/profile/mod.rs index 716ba71617..c15dcb62c5 100644 --- a/libsplinter/src/biome/profile/mod.rs +++ b/libsplinter/src/biome/profile/mod.rs @@ -14,6 +14,4 @@ //! Biome functionality to support user profiles. -#[cfg(feature = "rest-api-actix-web-1")] -pub mod rest_api; pub mod store; diff --git a/libsplinter/src/biome/profile/rest_api/mod.rs b/libsplinter/src/biome/profile/rest_api/mod.rs index cf21d74256..9e1a5bf09b 100644 --- a/libsplinter/src/biome/profile/rest_api/mod.rs +++ b/libsplinter/src/biome/profile/rest_api/mod.rs @@ -12,13 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod actix_web_1; - -#[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; - -pub use actix_web_1::BiomeProfileRestResourceProvider; - #[cfg(feature = "authorization")] const BIOME_PROFILE_READ_PERMISSION: Permission = Permission::Check { permission_id: "biome.profile.read", diff --git a/libsplinter/src/lib.rs b/libsplinter/src/lib.rs index 0d29e3aaf8..5adf38f5f2 100644 --- a/libsplinter/src/lib.rs +++ b/libsplinter/src/lib.rs @@ -17,11 +17,11 @@ #[macro_use] extern crate log; -#[cfg(any(feature = "admin-service", feature = "rest-api", feature = "registry"))] +#[cfg(any(feature = "admin-service", feature = "registry"))] #[macro_use] extern crate serde_derive; #[macro_use] -#[cfg(feature = "rest-api-actix-web-1")] +#[cfg(feature = "biome-client")] extern crate serde_json; #[macro_use] #[cfg(all( @@ -108,11 +108,7 @@ pub mod peer; pub mod protocol; pub mod protos; pub mod public_key; -#[cfg(all( - feature = "rest-api", - feature = "authorization", - feature = "authorization-handler-rbac" -))] +#[cfg(all(feature = "authorization", feature = "authorization-handler-rbac"))] pub mod rbac; #[cfg(feature = "registry")] pub mod registry; diff --git a/libsplinter/src/oauth/builder/openid.rs b/libsplinter/src/oauth/builder/openid.rs index 5772130c13..c43386d36a 100644 --- a/libsplinter/src/oauth/builder/openid.rs +++ b/libsplinter/src/oauth/builder/openid.rs @@ -13,6 +13,7 @@ // limitations under the License. use reqwest::blocking::Client; +use serde::Deserialize; use crate::error::{InternalError, InvalidStateError}; use crate::oauth::OpenIdProfileProvider; diff --git a/libsplinter/src/oauth/mod.rs b/libsplinter/src/oauth/mod.rs index 505562852a..b5a423e98f 100644 --- a/libsplinter/src/oauth/mod.rs +++ b/libsplinter/src/oauth/mod.rs @@ -17,8 +17,6 @@ mod builder; mod error; mod profile; -#[cfg(feature = "rest-api-actix-web-1")] -pub(crate) mod rest_api; pub mod store; mod subject; diff --git a/libsplinter/src/oauth/profile/openid.rs b/libsplinter/src/oauth/profile/openid.rs index ad0d4c6f03..2cf2ce01df 100644 --- a/libsplinter/src/oauth/profile/openid.rs +++ b/libsplinter/src/oauth/profile/openid.rs @@ -122,210 +122,3 @@ impl From for Profile { } } } - -#[cfg(test)] -mod tests { - use super::*; - - use std::sync::mpsc::channel; - use std::thread::JoinHandle; - - use actix::System; - use actix_web::{dev::Server, web, App, HttpRequest, HttpResponse, HttpServer}; - use futures::Future; - - const USERINFO_ENDPOINT: &str = "/userinfo"; - const ALL_DETAILS_TOKEN: &str = "all_details"; - const ONLY_SUB_TOKEN: &str = "only_sub"; - const UNEXPECTED_RESPONSE_CODE_TOKEN: &str = "unexpected_response_code"; - const INVALID_RESPONSE_TOKEN: &str = "invalid_response"; - const SUB: &str = "sub"; - const NAME: &str = "name"; - const GIVEN_NAME: &str = "given_name"; - const FAMILY_NAME: &str = "family_name"; - const EMAIL: &str = "email"; - const PICTURE: &str = "picture"; - - /// Verifies that the OpenID profile provider correctly returns all relevant profile information - /// when it's provided. - /// - /// 1. Start the mock OpenID server - /// 2. Get the profile for a user with all details filled out - /// 3. Verify that all profile details are correct - /// 4. Shutdown the OpenID server - #[test] - fn all_details() { - let (shutdown_handle, address) = run_mock_openid_server("all_details"); - - let profile = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) - .get_profile(ALL_DETAILS_TOKEN) - .expect("Failed to get profile") - .expect("Profile not found"); - - assert_eq!(&profile.subject, SUB); - assert_eq!(profile.name.as_deref(), Some(NAME)); - assert_eq!(profile.given_name.as_deref(), Some(GIVEN_NAME)); - assert_eq!(profile.family_name.as_deref(), Some(FAMILY_NAME)); - assert_eq!(profile.email.as_deref(), Some(EMAIL)); - assert_eq!(profile.picture.as_deref(), Some(PICTURE)); - - shutdown_handle.shutdown(); - } - - /// Verifies that the OpenID profile provider correctly returns the profile when only the - /// subject is provided - /// - /// 1. Start the mock OpenID server - /// 2. Get the profile for a user with only the subject filled out - /// 3. Verify that the `subject` field is correct and all other fields are empty - /// 4. Shutdown the OpenID server - #[test] - fn only_sub() { - let (shutdown_handle, address) = run_mock_openid_server("only_sub"); - - let profile = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) - .get_profile(ONLY_SUB_TOKEN) - .expect("Failed to get profile") - .expect("Profile not found"); - - assert_eq!(&profile.subject, SUB); - assert!(profile.name.is_none()); - assert!(profile.given_name.is_none()); - assert!(profile.family_name.is_none()); - assert!(profile.email.is_none()); - assert!(profile.picture.is_none()); - - shutdown_handle.shutdown(); - } - - /// Verifies that the OpenID profile provider correctly returns `Ok(None)` when receiving a - /// `401 Unauthorized` response from the OpenID server (which means the token is unknown). - /// - /// 1. Start the mock OpenID server - /// 2. Attempt to get the profile for an unknown token - /// 3. Verify that the profile provider returns the correct value - /// 4. Shutdown the OpenID server - #[test] - fn unauthorized_token() { - let (shutdown_handle, address) = run_mock_openid_server("unauthorized_token"); - - let profile_opt = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) - .get_profile("unknown_token") - .expect("Failed to get profile"); - - assert!(profile_opt.is_none()); - - shutdown_handle.shutdown(); - } - - /// Verifies that the OpenID profile provider correctly returns an error when receiving an - /// unexpected response code from the OpenID server. - /// - /// 1. Start the mock OpenID server - /// 2. Attempt to get the profile for a token that the server will return a non-200 and non-401 - /// response for - /// 3. Verify that the profile provider returns an error - /// 4. Shutdown the OpenID server - #[test] - fn unexpected_response_code() { - let (shutdown_handle, address) = run_mock_openid_server("unauthorized_token"); - - let profile_res = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) - .get_profile(UNEXPECTED_RESPONSE_CODE_TOKEN); - - assert!(profile_res.is_err()); - - shutdown_handle.shutdown(); - } - - /// Verifies that the OpenID profile provider correctly returns an error when receiving a - /// response that doesn't contain the `sub` field. - /// - /// 1. Start the mock OpenID server - /// 2. Attempt to get the profile for a token that the server will return an invalid response - /// for - /// 3. Verify that the profile provider returns an error - /// 4. Shutdown the OpenID server - #[test] - fn invalid_response() { - let (shutdown_handle, address) = run_mock_openid_server("unauthorized_token"); - - let profile_res = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) - .get_profile(INVALID_RESPONSE_TOKEN); - - assert!(profile_res.is_err()); - - shutdown_handle.shutdown(); - } - - /// Runs a mock OAuth OpenID server and returns its shutdown handle along with the address the - /// server is running on. - fn run_mock_openid_server(test_name: &str) -> (OpenIDServerShutdownHandle, String) { - let (tx, rx) = channel(); - - let instance_name = format!("OpenID-Server-{}", test_name); - let join_handle = std::thread::Builder::new() - .name(instance_name.clone()) - .spawn(move || { - let sys = System::new(instance_name); - let server = HttpServer::new(|| { - App::new().service(web::resource(USERINFO_ENDPOINT).to(userinfo_endpoint)) - }) - .bind("127.0.0.1:0") - .expect("Failed to bind OpenID server"); - let address = format!("http://127.0.0.1:{}", server.addrs()[0].port()); - let server = server.disable_signals().system_exit().start(); - tx.send((server, address)).expect("Failed to send server"); - sys.run().expect("OpenID server runtime failed"); - }) - .expect("Failed to spawn OpenID server thread"); - - let (server, address) = rx.recv().expect("Failed to receive server"); - - (OpenIDServerShutdownHandle(server, join_handle), address) - } - - /// The handler for the OpenID server's user info endpoint. - fn userinfo_endpoint(req: HttpRequest) -> HttpResponse { - match req - .headers() - .get("Authorization") - .and_then(|auth| auth.to_str().ok()) - .and_then(|auth_str| auth_str.strip_prefix("Bearer ")) - { - Some(token) if token == ALL_DETAILS_TOKEN => HttpResponse::Ok() - .content_type("application/json") - .json(json!({ - "sub": SUB, - "name": NAME, - "given_name": GIVEN_NAME, - "family_name": FAMILY_NAME, - "email": EMAIL, - "picture": PICTURE, - })), - Some(token) if token == ONLY_SUB_TOKEN => HttpResponse::Ok() - .content_type("application/json") - .json(json!({ - "sub": SUB, - })), - Some(token) if token == UNEXPECTED_RESPONSE_CODE_TOKEN => { - HttpResponse::BadRequest().finish() - } - Some(token) if token == INVALID_RESPONSE_TOKEN => HttpResponse::Ok().finish(), - Some(_) => HttpResponse::Unauthorized().finish(), - None => HttpResponse::BadRequest().finish(), - } - } - - struct OpenIDServerShutdownHandle(Server, JoinHandle<()>); - - impl OpenIDServerShutdownHandle { - pub fn shutdown(self) { - self.0 - .stop(false) - .wait() - .expect("Failed to stop OpenID server"); - self.1.join().expect("OpenID server thread failed"); - } - } -} diff --git a/libsplinter/src/oauth/subject/github.rs b/libsplinter/src/oauth/subject/github.rs index 2456f41028..3ef8c5ecdd 100644 --- a/libsplinter/src/oauth/subject/github.rs +++ b/libsplinter/src/oauth/subject/github.rs @@ -15,6 +15,7 @@ //! A subject provider that looks up GitHub usernames use reqwest::{blocking::Client, StatusCode}; +use serde::Deserialize; use crate::error::InternalError; diff --git a/libsplinter/src/oauth/subject/openid.rs b/libsplinter/src/oauth/subject/openid.rs index c8469a2291..5a1a464db0 100644 --- a/libsplinter/src/oauth/subject/openid.rs +++ b/libsplinter/src/oauth/subject/openid.rs @@ -15,6 +15,7 @@ //! A subject provider that looks up OpenID subject identifiers use reqwest::{blocking::Client, StatusCode}; +use serde::Deserialize; use crate::error::InternalError; diff --git a/libsplinter/src/rbac/store/identity.rs b/libsplinter/src/rbac/store/identity.rs index 36648c226f..7f6f85740a 100644 --- a/libsplinter/src/rbac/store/identity.rs +++ b/libsplinter/src/rbac/store/identity.rs @@ -20,18 +20,3 @@ pub enum Identity { /// A user ID-based identity. User(String), } - -impl From<&crate::rest_api::auth::identity::Identity> for Option { - fn from(identity: &crate::rest_api::auth::identity::Identity) -> Self { - match identity { - // RoleBasedAuthorization does not currently support custom identities - crate::rest_api::auth::identity::Identity::Custom(_) => None, - crate::rest_api::auth::identity::Identity::Key(key) => { - Some(Identity::Key(key.to_string())) - } - crate::rest_api::auth::identity::Identity::User(user_id) => { - Some(Identity::User(user_id.to_string())) - } - } - } -} diff --git a/libsplinter/src/registry/yaml/remote.rs b/libsplinter/src/registry/yaml/remote.rs index 4ffce6cfeb..911987c087 100644 --- a/libsplinter/src/registry/yaml/remote.rs +++ b/libsplinter/src/registry/yaml/remote.rs @@ -428,927 +428,3 @@ impl ShutdownHandle for RemoteYamlShutdownHandle { Ok(()) } } - -#[cfg(all(test, feature = "rest-api-actix-web-1"))] -mod tests { - use super::*; - - use std::fs::File; - - use actix_web::HttpResponse; - use futures::future::IntoFuture; - use tempfile::{Builder, TempDir}; - - use crate::rest_api::actix_web_1::{Method, Resource, RestApiBuilder, RestApiShutdownHandle}; - #[cfg(feature = "authorization")] - use crate::rest_api::auth::authorization::Permission; - - /// Verifies that a remote file that contains two nodes with the same identity is rejected (not - /// loaded). - #[test] - fn duplicate_identity() { - let mut registry = mock_registry(); - registry[0].identity = "identity".into(); - registry[1].identity = "identity".into(); - let test_config = TestConfig::setup("duplicate_identity", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains two nodes with the same endpoint is rejected (not - /// loaded). - #[test] - fn duplicate_endpoint() { - let mut registry = mock_registry(); - registry[0].endpoints = vec!["endpoint".into()]; - registry[1].endpoints = vec!["endpoint".into()]; - let test_config = TestConfig::setup("duplicate_endpoint", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains a node with an empty string as its identity is - /// rejected (not loaded). - #[test] - fn empty_identity() { - let mut registry = mock_registry(); - registry[0].identity = "".into(); - let test_config = TestConfig::setup("empty_identity", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains a node with an empty string in its endpoints is - /// rejected (not loaded). - #[test] - fn empty_endpoint() { - let mut registry = mock_registry(); - registry[0].endpoints = vec!["".into()]; - let test_config = TestConfig::setup("empty_endpoint", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains a node with an empty string as its display name is - /// rejected (not loaded). - #[test] - fn empty_display_name() { - let mut registry = mock_registry(); - registry[0].display_name = "".into(); - let test_config = TestConfig::setup("empty_display_name", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains a node with an empty string in its keys is - /// rejected (not loaded). - #[test] - fn empty_key() { - let mut registry = mock_registry(); - registry[0].keys = vec!["".into()]; - let test_config = TestConfig::setup("empty_key", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains a node with no endpoints is rejected (not loaded). - #[test] - fn missing_endpoints() { - let mut registry = mock_registry(); - registry[0].endpoints = vec![]; - let test_config = TestConfig::setup("missing_endpoints", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that a remote file that contains a node with no keys is rejected (not loaded). - #[test] - fn missing_keys() { - let mut registry = mock_registry(); - registry[0].keys = vec![]; - let test_config = TestConfig::setup("missing_keys", Some(registry)); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `fetch_node` with an existing identity returns the correct node. - #[test] - fn fetch_node_ok() { - let test_config = TestConfig::setup("fetch_node_ok", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let expected_node = mock_registry().pop().expect("Failed to get expected node"); - let node = remote_registry - .get_node(&expected_node.identity) - .expect("Failed to fetch node") - .expect("Node not found"); - assert_eq!(node, expected_node); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `fetch_node` with a non-existent identity returns Ok(None) - #[test] - fn fetch_node_not_found() { - let test_config = TestConfig::setup("fetch_node_not_found", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - assert!(remote_registry - .get_node("NodeNotInRegistry") - .expect("Failed to fetch node") - .is_none()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// - /// Verifies that `has_node` properly determines if a node exists in the registry. - /// - #[test] - fn has_node() { - let test_config = TestConfig::setup("has_node", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let expected_node = mock_registry().pop().expect("Failed to get expected node"); - assert!(remote_registry - .has_node(&expected_node.identity) - .expect("Failed to check if expected_node exists")); - assert!(!remote_registry - .has_node("NodeNotInRegistry") - .expect("Failed to check for non-existent node")); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `list_nodes` returns all nodes in the remote file. - #[test] - fn list_nodes() { - let test_config = TestConfig::setup("list_nodes", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let nodes = remote_registry - .list_nodes(&[]) - .expect("Failed to retrieve nodes") - .collect::>(); - - assert_eq!(nodes.len(), mock_registry().len()); - for node in mock_registry() { - assert!(nodes.contains(&node)); - } - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `list_nodes` returns an empty list when there are no nodes in the remote file. - #[test] - fn list_nodes_empty() { - let test_config = TestConfig::setup("list_nodes_empty", Some(vec![])); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let nodes = remote_registry - .list_nodes(&[]) - .expect("Failed to retrieve nodes") - .collect::>(); - - assert!(nodes.is_empty()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `list_nodes` returns the correct nodes when a metadata filter is provided. - #[test] - fn list_nodes_filter_metadata() { - let test_config = TestConfig::setup("list_nodes_filter_metadata", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let filter = vec![MetadataPredicate::Eq( - "company".into(), - mock_registry()[0] - .metadata - .get("company") - .expect("company metadata not set") - .into(), - )]; - - let nodes = remote_registry - .list_nodes(&filter) - .expect("Failed to retrieve nodes") - .collect::>(); - - assert_eq!(nodes.len(), 1); - assert_eq!(nodes[0], mock_registry()[0]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `list_nodes` returns the correct nodes when multiple metadata filters are - /// provided. - #[test] - fn list_nodes_filter_multiple() { - let test_config = TestConfig::setup("list_nodes_filter_multiple", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let filter = vec![ - MetadataPredicate::Eq( - "company".to_string(), - mock_registry()[2] - .metadata - .get("company") - .unwrap() - .to_string(), - ), - MetadataPredicate::Eq( - "admin".to_string(), - mock_registry()[2] - .metadata - .get("admin") - .unwrap() - .to_string(), - ), - ]; - - let nodes = remote_registry - .list_nodes(&filter) - .expect("Failed to retrieve nodes") - .collect::>(); - - assert_eq!(nodes.len(), 1); - assert_eq!(nodes[0], mock_registry()[2]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that `list_nodes` returns an empty list when no nodes fit the filtering criteria. - #[test] - fn list_nodes_filter_empty() { - let test_config = TestConfig::setup("list_nodes_filter_empty", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let filter = vec![MetadataPredicate::Eq( - "admin".to_string(), - "not an admin".to_string(), - )]; - - let nodes = remote_registry - .list_nodes(&filter) - .expect("Failed to retrieve nodes") - .collect::>(); - - assert!(nodes.is_empty()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that when the remote file is available at startup, it's fetched and cached - /// successfully. The internal list of nodes and the backing file should match the remote file. - #[test] - fn file_available_at_startup() { - let test_config = TestConfig::setup("file_available_at_startup", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that when the remote file is not available at startup, the registry starts up with - /// an empty cache. When the remote file becomes available, it should be fetched and cached on - /// the next read. - #[test] - fn file_unavailable_at_startup() { - // Start without a remote file - let test_config = TestConfig::setup("file_unavailable_at_startup", None); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - // Verify that the registry is still empty - verify_internal_cache(&test_config, &remote_registry, vec![]); - - // Make the remote file available now - test_config.update_registry(Some(mock_registry())); - - // Verify that the registry's contents were updated - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that when auto refresh is turned off, the auto refresh thread is not running. - #[test] - fn auto_refresh_disabled() { - let test_config = TestConfig::setup("auto_refresh_disabled", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - - // The `running` atomic bool is only set if the auto refresh thread was started. - assert!(shutdown_handle.running.is_none()); - - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that when auto refresh is turned on, the auto refresh thread is running and - /// refreshes the registry in the background - #[test] - fn auto_refresh_enabled() { - let test_config = TestConfig::setup("auto_refresh_enabled", Some(mock_registry())); - - let refresh_period = Duration::from_secs(1); - let mut remote_registry = RemoteYamlRegistry::new( - test_config.url(), - test_config.path(), - Some(refresh_period), - None, - ) - .expect("Failed to create registry"); - - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - - // The `running` atomic bool is only set if the auto refresh thread was started. - assert!(shutdown_handle.running.is_some()); - - test_config.update_registry(Some(vec![])); - - // Wait twice as long as the auto refresh period to be sure it has a chance to refresh - std::thread::sleep(refresh_period * 2); - - // Verify that the registry's contents were updated - verify_internal_cache(&test_config, &remote_registry, vec![]); - - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that when forced refresh feature is disabled, the registry is not refreshed on - /// read. - #[test] - fn forced_refresh_disabled() { - let test_config = TestConfig::setup("forced_refresh_disabled", Some(mock_registry())); - - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - test_config.update_registry(Some(vec![])); - - // Verify that the registry's contents are the same as before, even though the remote file - // was updated - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that when forced refresh is turned on, the registry refreshes on read after the - /// refresh period has elapsed. - #[test] - fn forced_refresh_enabled() { - let test_config = TestConfig::setup("forced_refresh_enabled", Some(mock_registry())); - - let refresh_period = Duration::from_millis(10); - let mut remote_registry = RemoteYamlRegistry::new( - test_config.url(), - test_config.path(), - None, - Some(refresh_period), - ) - .expect("Failed to create registry"); - - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - test_config.update_registry(Some(vec![])); - - // Wait at least as long as the forced refresh period - std::thread::sleep(refresh_period); - - // Verify that the registry's contents are updated on read - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that any changes made to the remote file are fetched on restart if the remote file - /// is available. - #[test] - fn restart_file_available() { - let test_config = TestConfig::setup("restart_file_available", Some(mock_registry())); - - // Start the registry the first time, verify its contents, and shut it down - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - - // Update the remote file - test_config.update_registry(Some(vec![])); - - // Start the registry again and verify that it has the updated registry contents - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - verify_internal_cache(&test_config, &remote_registry, vec![]); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - /// Verifies that if the remote file is not available when the registry restarts, the old - /// contents will still be available. - #[test] - fn restart_file_unavailable() { - let test_config = TestConfig::setup("restart_file_unavailable", Some(mock_registry())); - - // Start the registry the first time, verify its contents, and shut it down - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - - // Make the remote file unavailable - test_config.update_registry(None); - - // Start the registry again and verify that the old contents are still available - let mut remote_registry = - RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) - .expect("Failed to create registry"); - verify_internal_cache(&test_config, &remote_registry, mock_registry()); - - let mut shutdown_handle = remote_registry - .take_shutdown_handle() - .expect("Unable to get shutdown handle"); - shutdown_handle.signal_shutdown(); - shutdown_handle - .wait_for_shutdown() - .expect("Unable to shutdown remote registry"); - test_config.shutdown(); - } - - // Restart, remote file not available - - /// Creates a mock registry. - fn mock_registry() -> Vec { - vec![ - Node::builder("Node-123") - .with_endpoint("tcps://12.0.0.123:8431") - .with_display_name("Bitwise IO - Node 1") - .with_key("abcd") - .with_metadata("company", "Bitwise IO") - .with_metadata("admin", "Bob") - .build() - .expect("Failed to build node1"), - Node::builder("Node-456") - .with_endpoint("tcps://12.0.0.123:8434") - .with_display_name("Cargill - Node 1") - .with_key("0123") - .with_metadata("company", "Cargill") - .with_metadata("admin", "Carol") - .build() - .expect("Failed to build node2"), - Node::builder("Node-789") - .with_endpoint("tcps://12.0.0.123:8435") - .with_display_name("Cargill - Node 2") - .with_key("4567") - .with_metadata("company", "Cargill") - .with_metadata("admin", "Charlie") - .build() - .expect("Failed to build node3"), - ] - } - - /// Verifies that the retrieved nodes and the backing file of the `remote_registry` match the - /// contents of the `expected_registry`. - fn verify_internal_cache( - test_config: &TestConfig, - remote_registry: &RemoteYamlRegistry, - expected_registry: Vec, - ) { - // Verify the internal list of nodes - assert_eq!( - remote_registry.get_nodes().expect("Failed to get nodes"), - expected_registry, - ); - - // Verify the backing file's contents - let filename = compute_cache_filename(test_config.url(), test_config.path()) - .expect("Failed to compute cache filename"); - let file = File::open(filename).expect("Failed to open cache file"); - let file_contents: Vec = - serde_yaml::from_reader(file).expect("Failed to deserialize cache file"); - - let file_contents_nodes: Vec = file_contents - .into_iter() - .map(|node| Node::try_from(node).expect("Unable to build node")) - .collect(); - assert_eq!(file_contents_nodes, expected_registry); - } - - /// Simplifies tests by handling some of the setup and tear down. - struct TestConfig { - _temp_dir: TempDir, - temp_dir_path: String, - registry: Arc>>>, - registry_url: String, - rest_api_shutdown_handle: RestApiShutdownHandle, - rest_api_join_handle: std::thread::JoinHandle<()>, - } - - impl TestConfig { - /// Setup for the test, using the `test_name` as the prefix for the temp directory and the - /// `registry` to populate the remote file (if `Some`, otherwise the remote file won't be - /// available). - fn setup(test_name: &str, registry: Option>) -> Self { - let temp_dir = Builder::new() - .prefix(test_name) - .tempdir() - .expect("Failed to create temp dir"); - let temp_dir_path = temp_dir - .path() - .to_str() - .expect("Failed to get path") - .to_string(); - - let registry = Arc::new(Mutex::new(registry)); - - let (rest_api_shutdown_handle, rest_api_join_handle, registry_url) = - serve_registry(registry.clone()); - - Self { - _temp_dir: temp_dir, - temp_dir_path, - registry, - registry_url, - rest_api_shutdown_handle, - rest_api_join_handle, - } - } - - /// Gets the temp directory's path - fn path(&self) -> &str { - &self.temp_dir_path - } - - /// Gets the URL for the registry file - fn url(&self) -> &str { - &self.registry_url - } - - /// Updates the `registry` file served up by the REST API; if `registry` is `None`, the - /// remote file won't be available. - fn update_registry(&self, registry: Option>) { - *self.registry.lock().expect("Registry lock poisonsed") = registry; - } - - /// Shuts down the REST API; this should be called at the end of every test that uses - /// `TestConfig`. - fn shutdown(self) { - self.rest_api_shutdown_handle - .shutdown() - .expect("Unable to shutdown rest api"); - self.rest_api_join_handle - .join() - .expect("Unable to join rest api thread"); - } - } - - /// Wraps `run_rest_api_on_open_port`, serving up the given `registry` as a registry YAML file - /// that can be fetched at the returned URL. If `registry` is `None`, the registry file will not - /// be available. - fn serve_registry( - registry: Arc>>>, - ) -> (RestApiShutdownHandle, std::thread::JoinHandle<()>, String) { - let mut resource = Resource::build("/registry.yaml"); - #[cfg(feature = "authorization")] - { - resource = resource.add_method( - Method::Get, - Permission::AllowUnauthenticated, - move |_, _| { - Box::new(match &*registry.lock().expect("Registry lock poisoned") { - Some(registry) => { - let yaml_registry: Vec = registry - .iter() - .map(|node| YamlNode::from(node.clone())) - .collect(); - HttpResponse::Ok() - .body( - serde_yaml::to_vec(&yaml_registry) - .expect("Failed to serialize registry file"), - ) - .into_future() - } - None => HttpResponse::NotFound().finish().into_future(), - }) - }, - ) - } - #[cfg(not(feature = "authorization"))] - { - resource = resource.add_method(Method::Get, move |_, _| { - Box::new(match &*registry.lock().expect("Registry lock poisoned") { - Some(registry) => { - let yaml_registry: Vec = registry - .iter() - .map(|node| YamlNode::from(node.clone())) - .collect(); - HttpResponse::Ok() - .body( - serde_yaml::to_vec(&yaml_registry) - .expect("Failed to serialize registry file"), - ) - .into_future() - } - None => HttpResponse::NotFound().finish().into_future(), - }) - }) - } - let (shutdown, join, url) = run_rest_api_on_open_port(vec![resource]); - - (shutdown, join, format!("http://{}/registry.yaml", url)) - } - - fn run_rest_api_on_open_port( - resources: Vec, - ) -> (RestApiShutdownHandle, std::thread::JoinHandle<()>, String) { - #[cfg(not(feature = "https-bind"))] - let bind = "127.0.0.1:0"; - #[cfg(feature = "https-bind")] - let bind = crate::rest_api::BindConfig::Http("127.0.0.1:0".into()); - - let result = RestApiBuilder::new() - .with_bind(bind) - .add_resources(resources.clone()) - .build_insecure() - .expect("Failed to build REST API") - .run_insecure(); - match result { - Ok((shutdown_handle, join_handle)) => { - let port = shutdown_handle.port_numbers()[0]; - (shutdown_handle, join_handle, format!("127.0.0.1:{}", port)) - } - Err(err) => panic!("Failed to run REST API: {}", err), - } - } -} diff --git a/libsplinter/src/rest_api/auth/authorization/permission_map/method.rs b/libsplinter/src/rest_api/auth/authorization/permission_map/method.rs deleted file mode 100644 index 68b7ade074..0000000000 --- a/libsplinter/src/rest_api/auth/authorization/permission_map/method.rs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018-2022 Cargill Incorporated -// -// 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. - -use crate::rest_api::actix_web_1::Method as Actix1Method; - -#[derive(PartialEq, Clone)] -pub enum Method { - Get, - Post, - Put, - Patch, - Delete, - Head, -} - -impl From<&Actix1Method> for Method { - fn from(source: &Actix1Method) -> Self { - match source { - Actix1Method::Get => Method::Get, - Actix1Method::Post => Method::Post, - Actix1Method::Put => Method::Put, - Actix1Method::Patch => Method::Patch, - Actix1Method::Delete => Method::Delete, - Actix1Method::Head => Method::Head, - } - } -} - -impl From for Method { - fn from(source: crate::rest_api::actix_web_1::Method) -> Self { - (&source).into() - } -} diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/mod.rs b/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/mod.rs deleted file mode 100644 index c9567df503..0000000000 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/mod.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018-2022 Cargill Incorporated -// -// 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. - -//! Role-based Authorization REST API resources. - -#[cfg(feature = "rest-api-actix-web-1")] -mod actix_web_1; -mod resources; - -use crate::rest_api::auth::Permission; - -#[cfg(feature = "rest-api-actix-web-1")] -pub use actix_web_1::RoleBasedAuthorizationResourceProvider; - -#[cfg(feature = "rest-api-actix-web-1")] -const RBAC_READ_PERMISSION: Permission = Permission::Check { - permission_id: "authorization.rbac.read", - permission_display_name: "RBAC read", - permission_description: "Allows the client to read roles, identities, and role assignments", -}; - -#[cfg(feature = "rest-api-actix-web-1")] -const RBAC_WRITE_PERMISSION: Permission = Permission::Check { - permission_id: "authorization.rbac.write", - permission_display_name: "RBAC write", - permission_description: "Allows the client to modify roles, identities, and role assignments", -}; diff --git a/libsplinter/src/rest_api/mod.rs b/libsplinter/src/rest_api/mod.rs index 03c6a881da..08dbcc11a8 100644 --- a/libsplinter/src/rest_api/mod.rs +++ b/libsplinter/src/rest_api/mod.rs @@ -12,119 +12,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Rest API Module. -//! -//! Module for creating REST APIs for services. -//! -//! Below is an example of a `struct` that implements `ResourceProvider` and then passes its resources -//! to a running instance of `RestApi`. -//! -//! ``` -//! use actix_web::HttpResponse; -//! use cylinder::{VerifierFactory, secp256k1::Secp256k1Context}; -//! use futures::IntoFuture; -//! use splinter::rest_api::{ -//! AuthConfig, Resource, Method, RestApiBuilder, RestResourceProvider, -//! auth::authorization::Permission, -//! }; -//! -//! struct IndexResource { -//! pub name: String -//! } -//! -//! impl RestResourceProvider for IndexResource { -//! fn resources(&self) -> Vec { -//! let name = self.name.clone(); -//! -//! vec![Resource::build("/index").add_method( -//! Method::Get, -//! Permission::AllowUnauthenticated, -//! move |r, p| { -//! Box::new( -//! HttpResponse::Ok() -//! .body(format!("Hello, I am {}", name)) -//! .into_future()) -//! }, -//! )] -//! } -//! } -//! -//! let index_resource = IndexResource { name: "Taco".to_string() }; -//! -//! #[cfg(not(feature = "https-bind"))] -//! let bind = "localhost:8080"; -//! #[cfg(feature = "https-bind")] -//! let bind = splinter::rest_api::BindConfig::Http("localhost:8080".into()); -//! -//! RestApiBuilder::new() -//! .add_resources(index_resource.resources()) -//! .with_bind(bind) -//! .with_auth_configs(vec![AuthConfig::Cylinder{ -//! verifier: Secp256k1Context::new().new_verifier(), -//! }]) -//! .build() -//! .unwrap() -//! .run(); -//! ``` - -#[cfg(feature = "rest-api-actix-web-1")] -pub mod actix_web_1; -pub mod auth; -mod bind_config; -#[cfg(feature = "rest-api-cors")] -pub mod cors; -mod errors; -#[cfg(feature = "oauth")] -mod oauth_config; -pub mod paging; -mod response_models; -pub mod secrets; -pub mod sessions; - -use percent_encoding::{AsciiSet, CONTROLS}; - -#[cfg(all(feature = "oauth", feature = "rest-api-actix-web-1"))] -use crate::oauth::rest_api::OAuthResourceProvider; - -pub use bind_config::BindConfig; -pub use errors::{RequestError, RestApiServerError}; -#[cfg(feature = "oauth")] -pub use oauth_config::OAuthConfig; - -pub use response_models::ErrorResponse; - -#[cfg(feature = "rest-api-actix-web-1")] -pub use actix_web_1::{ - get_authorization_token, into_bytes, into_protobuf, new_websocket_event_sender, require_header, - AuthConfig, Continuation, EventSender, HandlerFunction, Method, ProtocolVersionRangeGuard, - Request, RequestGuard, Resource, Response, ResponseError, RestApi, RestApiBuilder, - RestApiShutdownHandle, RestResourceProvider, -}; - -#[cfg(any( - feature = "admin-service-event-client-actix-web-client", - feature = "authorization", - feature = "biome-credentials", - feature = "biome-key-management", - all(feature = "oauth", feature = "rest-api-actix-web-1"), -))] +#[cfg(feature = "admin-service-event-client-actix-web-client")] pub(crate) const SPLINTER_PROTOCOL_VERSION: u32 = 2; - -const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS - .add(b' ') - .add(b'"') - .add(b'<') - .add(b'>') - .add(b'`') - .add(b'=') - .add(b'!') - .add(b'{') - .add(b'}') - .add(b'[') - .add(b']') - .add(b':') - .add(b','); - -pub fn percent_encode_filter_query(input: &str) -> String { - percent_encoding::utf8_percent_encode(input, QUERY_ENCODE_SET).to_string() -} diff --git a/libsplinter/src/rest_api/paging.rs b/libsplinter/src/rest_api/paging.rs deleted file mode 100644 index 2eb286acaf..0000000000 --- a/libsplinter/src/rest_api/paging.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright (c) 2019 Target Brands, Inc. -// Copyright 2018-2022 Cargill Incorporated -// -// 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. - -pub const DEFAULT_LIMIT: usize = 100; -pub const DEFAULT_OFFSET: usize = 0; - -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] -pub struct Paging { - pub current: String, - pub offset: usize, - pub limit: usize, - pub total: usize, - pub first: String, - pub prev: String, - pub next: String, - pub last: String, -} - -pub struct PagingBuilder { - link: String, - limit: Option, - offset: Option, - query_count: usize, -} - -impl PagingBuilder { - pub fn new(link: String, query_count: usize) -> PagingBuilder { - PagingBuilder { - link, - limit: None, - offset: None, - query_count, - } - } -} - -impl PagingBuilder { - pub fn with_limit(self, limit: usize) -> Self { - Self { - limit: Some(limit), - ..self - } - } - - pub fn with_offset(self, offset: usize) -> Self { - Self { - offset: Some(offset), - ..self - } - } - - pub fn build(self) -> Paging { - let limit = self.limit.unwrap_or(DEFAULT_LIMIT); - let offset = self.offset.unwrap_or(DEFAULT_OFFSET); - let link = self.link; - - let base_link = { - // if the link does not already contain ? add it to the end - if !link.contains('?') { - format!("{}?limit={}&", link, limit) - } else { - format!("{}limit={}&", link, limit) - } - }; - - let current_link = format!("{}offset={}", base_link, offset); - - let first_link = format!("{}offset=0", base_link); - - let previous_offset = if offset > limit { offset - limit } else { 0 }; - - let previous_link = format!("{}offset={}", base_link, previous_offset); - - let query_count = self.query_count; - let last_offset = if query_count > 0 { - ((query_count - 1) / limit) * limit - } else { - 0 - }; - let last_link = format!("{}offset={}", base_link, last_offset); - - let next_offset = if offset + limit > last_offset { - last_offset - } else { - offset + limit - }; - - let next_link = format!("{}offset={}", base_link, next_offset); - - Paging { - current: current_link, - offset, - limit, - total: query_count, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const TEST_LINK: &str = "/api/test?"; - - #[test] - fn test_default_paging_response() { - // Create paging response from default limit, default offset, a total of 1000 - let test_paging_response = PagingBuilder::new(TEST_LINK.to_string(), 1000).build(); - let generated_paging_response = - create_test_paging_response(DEFAULT_OFFSET, DEFAULT_LIMIT, 100, 0, 900); - assert_eq!(test_paging_response, generated_paging_response); - } - - #[test] - fn test_50offset_paging_response() { - // Create paging response from default limit, offset of 50, and a total of 1000 - let test_paging_response = PagingBuilder::new(TEST_LINK.to_string(), 1000) - .with_offset(50) - .build(); - let generated_paging_response = create_test_paging_response(50, DEFAULT_LIMIT, 150, 0, 900); - assert_eq!(test_paging_response, generated_paging_response); - } - - #[test] - fn test_550offset_paging_response() { - // Create paging response from default limit, offset value of 150, and a total of 1000 - let test_paging_response = PagingBuilder::new(TEST_LINK.to_string(), 1000) - .with_offset(550) - .build(); - let generated_paging_response = - create_test_paging_response(550, DEFAULT_LIMIT, 650, 450, 900); - assert_eq!(test_paging_response, generated_paging_response); - } - - #[test] - fn test_950offset_paging_response() { - // Create paging response from default limit, offset value of 950, and a total of 1000 - let test_paging_response = PagingBuilder::new(TEST_LINK.to_string(), 1000) - .with_offset(950) - .build(); - let generated_paging_response = - create_test_paging_response(950, DEFAULT_LIMIT, 900, 850, 900); - assert_eq!(test_paging_response, generated_paging_response); - } - - #[test] - fn test_50limit_paging_response() { - // Create paging response from default limit, offset of 50, and a total of 1000 - let test_paging_response = PagingBuilder::new(TEST_LINK.to_string(), 1000) - .with_limit(50) - .build(); - let generated_paging_response = create_test_paging_response(DEFAULT_OFFSET, 50, 50, 0, 950); - assert_eq!(test_paging_response, generated_paging_response); - } - - #[test] - fn test_50limit_150offset_paging_response() { - // Create paging response from limit of 50, offset of 150, and total of 1000 - let test_paging_response = PagingBuilder::new(TEST_LINK.to_string(), 1000) - .with_limit(50) - .with_offset(150) - .build(); - let generated_paging_response = create_test_paging_response(150, 50, 200, 100, 950); - assert_eq!(test_paging_response, generated_paging_response); - } - - fn create_test_paging_response( - offset: usize, - limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, - ) -> Paging { - // Creates a generated paging response from the limit and offset values passed into the function - let base_link = format!("{}limit={}&", TEST_LINK, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total: 1000, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } - } -} diff --git a/libsplinter/src/service/instance/factory/mod.rs b/libsplinter/src/service/instance/factory/mod.rs index ee2e5d54b9..2414592c2f 100644 --- a/libsplinter/src/service/instance/factory/mod.rs +++ b/libsplinter/src/service/instance/factory/mod.rs @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "rest-api-actix-web-1")] -mod endpoint; mod service; -#[cfg(feature = "rest-api-actix-web-1")] -pub use endpoint::EndpointFactory; pub use service::ServiceFactory; diff --git a/libsplinter/src/service/instance/mod.rs b/libsplinter/src/service/instance/mod.rs index acfbfbcd5f..8b9e1ea4b7 100644 --- a/libsplinter/src/service/instance/mod.rs +++ b/libsplinter/src/service/instance/mod.rs @@ -21,8 +21,6 @@ mod orchestrator; mod service_instance; mod validation; -#[cfg(feature = "rest-api-actix-web-1")] -pub use factory::EndpointFactory; pub use factory::ServiceFactory; pub use message_context::ServiceMessageContext; pub use network_registry::ServiceNetworkRegistry; diff --git a/libsplinter/src/service/mod.rs b/libsplinter/src/service/mod.rs index 2cff88a552..ebd1a03093 100644 --- a/libsplinter/src/service/mod.rs +++ b/libsplinter/src/service/mod.rs @@ -49,8 +49,6 @@ mod message_handler_factory; mod message_sender; #[cfg(feature = "service-message-sender-factory")] mod message_sender_factory; -#[cfg(feature = "rest-api-actix-web-1")] -pub mod rest_api; mod routable; mod service_type; #[cfg(feature = "service-timer-alarm")] diff --git a/rest_api/actix_web_1/Cargo.toml b/rest_api/actix_web_1/Cargo.toml index dc5931ae7f..bc88877c17 100644 --- a/rest_api/actix_web_1/Cargo.toml +++ b/rest_api/actix_web_1/Cargo.toml @@ -23,20 +23,33 @@ description = """\ """ [dependencies] +actix = { version = "0.8", default-features = false } +actix-web-actors = { version = "1", optional = true } actix-web = { version = "1" } +awc = { version = "0.2", optional = true, default-features = false } +cylinder = { version = "0.2.1", optional = true } futures = { version = "0.1" } -log = { version = "0.4", optional = true } +jsonwebtoken = { version = "7.0", optional = true } +log = { version = "0.4" } +openssl = { version = "0.10", optional = true } scabbard = { path = "../../services/scabbard/libscabbard", optional = true } +rand = "0.8" serde = { version = "1", features = ["derive"], optional = true } -serde_json = { version = "1", optional = true } +serde_json = { version = "1" } splinter = { path = "../../libsplinter", features = ["rest-api-actix-web-1"] } splinter-rest-api-common = { path = "../common" } transact = { version = "0.5", features = ["state-merkle-sql", "family-sabre"], optional = true } +uuid = { version = "0.8", features = ["v4", "v5"] } +protobuf = "2.23" [dev-dependencies] -diesel = { version = "1", features = ["r2d2", "serde_json", "sqlite"] } +diesel = { version = "1", features = ["r2d2", "sqlite"] } splinter = { path = "../../libsplinter", features = ["admin-service", "rest-api-actix-web-1", "sqlite"] } reqwest = { version = "0.11", features = ["blocking", "json"] } +url = "1.7.1" +actix-http = { version = "0.2", default-features = false } +tempfile = "3" +oauth2 = { version = "4" } [features] default = [] @@ -50,11 +63,15 @@ stable = [ "biome", "biome-credentials", "biome-key-management", + "biome-profile", "oauth", + "rbac", "registry", "rest-api", + "rest-api-cors", "scabbard-service", "service", + "websocket", ] experimental = [ @@ -64,18 +81,25 @@ experimental = [ ] admin-service = [ - "log", "serde", - "serde_json", "splinter/admin-service" + ] -authorization = ["splinter/authorization", "splinter-rest-api-common/authorization"] -biome = ["splinter/biome", "serde"] -biome-credentials = ["biome"] +authorization = ["splinter/authorization","splinter-rest-api-common/authorization"] +authorization-handler-rbac = ["splinter-rest-api-common/authorization-handler-rbac", "authorization"] +biome = ["splinter/biome", "splinter-rest-api-common/biome", "serde"] +biome-credentials = ["biome", "splinter/biome-credentials", "splinter-rest-api-common/biome-credentials", "jsonwebtoken"] biome-key-management = ["biome", "splinter/biome-key-management"] -registry = ["splinter/registry"] -oauth = ["log"] +biome-profile = ["splinter/biome-profile"] +cylinder-jwt = ["cylinder","splinter-rest-api-common/cylinder-jwt"] +https-bind = ["openssl", "splinter-rest-api-common/https-bind","actix-web/ssl"] +oauth = ["biome", "splinter/oauth","splinter-rest-api-common/oauth"] +rbac = ["splinter/authorization-handler-rbac","splinter-rest-api-common/rbac"] +registry = ["splinter/registry", "splinter-rest-api-common/registry"] +registry-remote = ["splinter/registry-remote","registry"] rest-api = ["splinter/rest-api"] -scabbard-service = ["scabbard/splinter-service", "scabbard/rest-api", "transact", "log"] -service = ["splinter/runtime-service", "serde_json", "log"] +rest-api-cors = [] +scabbard-service = ["scabbard/splinter-service", "scabbard/rest-api", "transact", "splinter-rest-api-common/scabbard"] +service = ["splinter/runtime-service"] service-endpoint = ["splinter-rest-api-common/service-endpoint"] +websocket = ["actix-web-actors"] diff --git a/rest_api/actix_web_1/src/admin/circuits.rs b/rest_api/actix_web_1/src/admin/circuits.rs index df7466a646..f463dc234e 100644 --- a/rest_api/actix_web_1/src/admin/circuits.rs +++ b/rest_api/actix_web_1/src/admin/circuits.rs @@ -21,13 +21,13 @@ use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::{future::IntoFuture, Future}; use std::collections::HashMap; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; use splinter::admin::store::{AdminServiceStore, CircuitPredicate, CircuitStatus}; -use splinter::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - paging::{PagingBuilder, DEFAULT_LIMIT, DEFAULT_OFFSET}, - ErrorResponse, +use splinter_rest_api_common::{ + paging::v1::{PagingBuilder, DEFAULT_LIMIT, DEFAULT_OFFSET}, + response_models::ErrorResponse, + SPLINTER_PROTOCOL_VERSION, }; -use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; use super::error::CircuitListError; use super::resources; @@ -290,6 +290,8 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; use serde_json::{to_value, Value as JsonValue}; + use crate::framework::AuthConfig; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; use splinter::admin::store::diesel::DieselAdminServiceStore; use splinter::admin::store::{ AuthorizationType, Circuit, CircuitBuilder, CircuitNode, CircuitNodeBuilder, @@ -297,16 +299,10 @@ mod tests { }; use splinter::error::InternalError; use splinter::migrations::run_sqlite_migrations; - use splinter::rest_api::actix_web_1::AuthConfig; - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, - }; - use splinter::rest_api::auth::identity::{Identity, IdentityProvider}; - use splinter::rest_api::auth::AuthorizationHeader; - use splinter::rest_api::{ - actix_web_1::{RestApiBuilder, RestApiShutdownHandle}, - paging::Paging, - }; + use splinter_rest_api_common::auth::AuthorizationHeader; + use splinter_rest_api_common::auth::{AuthorizationHandler, AuthorizationHandlerResult}; + use splinter_rest_api_common::auth::{Identity, IdentityProvider}; + use splinter_rest_api_common::paging::v1::Paging; #[test] /// Tests a GET /admin/circuits request with no filters returns the expected circuits. @@ -433,7 +429,7 @@ mod tests { 0, 0, 1, - &format!("/admin/circuits?filter=node_1&"), + &"/admin/circuits?filter=node_1&".to_string(), )) .expect("failed to convert expected paging") ); @@ -481,7 +477,7 @@ mod tests { 0, 0, 1, - &format!("/admin/circuits?status=disbanded&"), + &"/admin/circuits?status=disbanded&".to_string(), )) .expect("failed to convert expected paging") ); @@ -530,7 +526,7 @@ mod tests { 0, 0, 1, - &format!("/admin/circuits?filter=node_5&status=disbanded&"), + &"/admin/circuits?filter=node_5&status=disbanded&".to_string(), )) .expect("failed to convert expected paging") ); @@ -577,7 +573,7 @@ mod tests { 0, 0, 0, - &format!("/admin/circuits?filter=node_5&status=active&"), + &"/admin/circuits?filter=node_5&status=active&".to_string(), )) .expect("failed to convert expected paging") ); @@ -681,29 +677,16 @@ mod tests { fn create_test_paging_response( offset: usize, limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, + _next_offset: usize, + _previous_offset: usize, + _last_offset: usize, total: usize, link: &str, ) -> Paging { - let base_link = format!("{}limit={}&", link, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } + Paging::builder(link.to_string(), total) + .with_limit(limit) + .with_offset(offset) + .build() } fn get_circuit_1() -> (Circuit, Vec) { @@ -729,7 +712,7 @@ mod tests { ( CircuitBuilder::new() - .with_circuit_id("abcde-12345".into()) + .with_circuit_id("abcde-12345") .with_authorization_type(&AuthorizationType::Trust) .with_members(&nodes) .with_roster(&[service]) diff --git a/rest_api/actix_web_1/src/admin/circuits_circuit_id.rs b/rest_api/actix_web_1/src/admin/circuits_circuit_id.rs index ab723f52bf..fc435046a4 100644 --- a/rest_api/actix_web_1/src/admin/circuits_circuit_id.rs +++ b/rest_api/actix_web_1/src/admin/circuits_circuit_id.rs @@ -19,12 +19,11 @@ use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::Future; use splinter::admin::store::AdminServiceStore; -use splinter::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, -}; +use splinter_rest_api_common::response_models::ErrorResponse; use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; + use super::error::CircuitFetchError; use super::resources; #[cfg(feature = "authorization")] @@ -132,6 +131,8 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; use serde_json::{to_value, Value as JsonValue}; + use crate::framework::AuthConfig; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; use splinter::admin::store::diesel::DieselAdminServiceStore; use splinter::admin::store::{ AuthorizationType, Circuit, CircuitBuilder, CircuitNode, CircuitNodeBuilder, @@ -139,13 +140,9 @@ mod tests { }; use splinter::error::InternalError; use splinter::migrations::run_sqlite_migrations; - use splinter::rest_api::actix_web_1::AuthConfig; - use splinter::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, - }; - use splinter::rest_api::auth::identity::{Identity, IdentityProvider}; - use splinter::rest_api::auth::AuthorizationHeader; + use splinter_rest_api_common::auth::AuthorizationHeader; + use splinter_rest_api_common::auth::{AuthorizationHandler, AuthorizationHandlerResult}; + use splinter_rest_api_common::auth::{Identity, IdentityProvider}; #[test] /// Tests a GET /admin/circuit/{circuit_id} request returns the expected circuit. diff --git a/rest_api/actix_web_1/src/admin/mod.rs b/rest_api/actix_web_1/src/admin/mod.rs index 25aff5b61e..3499496337 100644 --- a/rest_api/actix_web_1/src/admin/mod.rs +++ b/rest_api/actix_web_1/src/admin/mod.rs @@ -19,14 +19,15 @@ mod proposals; mod proposals_circuit_id; mod resources; mod submit; +#[cfg(feature = "websocket")] mod ws_register_type; +use crate::framework::Resource; +use crate::framework::RestResourceProvider; use splinter::admin::service::AdminService; use splinter::admin::store::AdminServiceStore; #[cfg(feature = "authorization")] -use splinter::rest_api::auth::authorization::Permission; -use splinter::rest_api::Resource; -use splinter::rest_api::RestResourceProvider; +use splinter_rest_api_common::auth::Permission; #[cfg(feature = "authorization")] const CIRCUIT_READ_PERMISSION: Permission = Permission::Check { @@ -48,6 +49,7 @@ pub struct AdminServiceRestProvider { impl AdminServiceRestProvider { pub fn new(source: &AdminService) -> Self { let resources = vec![ + #[cfg(feature = "websocket")] ws_register_type::make_application_handler_registration_route(source.commands()), submit::make_submit_route(source.commands()), proposals_circuit_id::make_fetch_proposal_resource(source.proposal_store_factory()), diff --git a/rest_api/actix_web_1/src/admin/proposals.rs b/rest_api/actix_web_1/src/admin/proposals.rs index e638525fdb..c42845fa5b 100644 --- a/rest_api/actix_web_1/src/admin/proposals.rs +++ b/rest_api/actix_web_1/src/admin/proposals.rs @@ -22,12 +22,13 @@ use futures::{future::IntoFuture, Future}; use splinter::admin::service::proposal_store::ProposalStoreFactory; use splinter::admin::store::CircuitPredicate; -use splinter::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - paging::{PagingBuilder, DEFAULT_LIMIT, DEFAULT_OFFSET}, - ErrorResponse, -}; use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; +use splinter_rest_api_common::{ + paging::v1::{PagingBuilder, DEFAULT_LIMIT, DEFAULT_OFFSET}, + response_models::ErrorResponse, +}; + +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; use super::error::ProposalListError; use super::resources; @@ -273,6 +274,8 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; use serde_json::{to_value, Value as JsonValue}; + use crate::framework::AuthConfig; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; use splinter::admin::{ messages::{ AuthorizationType, CircuitProposal, CircuitStatus, CreateCircuit, DurabilityType, @@ -288,16 +291,10 @@ mod tests { }; use splinter::error::InternalError; use splinter::public_key::PublicKey; - use splinter::rest_api::actix_web_1::AuthConfig; - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, - }; - use splinter::rest_api::auth::identity::{Identity, IdentityProvider}; - use splinter::rest_api::auth::AuthorizationHeader; - use splinter::rest_api::{ - actix_web_1::{RestApiBuilder, RestApiShutdownHandle}, - paging::Paging, - }; + use splinter_rest_api_common::auth::AuthorizationHeader; + use splinter_rest_api_common::auth::{AuthorizationHandler, AuthorizationHandlerResult}; + use splinter_rest_api_common::auth::{Identity, IdentityProvider}; + use splinter_rest_api_common::paging::v1::Paging; #[test] /// Tests a GET /admin/proposals request with no filters returns the expected proposals. @@ -672,29 +669,16 @@ mod tests { fn create_test_paging_response( offset: usize, limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, + _next_offset: usize, + _previous_offset: usize, + _last_offset: usize, total: usize, link: &str, ) -> Paging { - let base_link = format!("{}limit={}&", link, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } + Paging::builder(link.to_string(), total) + .with_limit(limit) + .with_offset(offset) + .build() } #[derive(Clone)] diff --git a/rest_api/actix_web_1/src/admin/proposals_circuit_id.rs b/rest_api/actix_web_1/src/admin/proposals_circuit_id.rs index 9b9f84062a..b37b3a14f2 100644 --- a/rest_api/actix_web_1/src/admin/proposals_circuit_id.rs +++ b/rest_api/actix_web_1/src/admin/proposals_circuit_id.rs @@ -21,12 +21,11 @@ use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::Future; use splinter::admin::service::proposal_store::ProposalStoreFactory; -use splinter::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, -}; +use splinter_rest_api_common::response_models::ErrorResponse; use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; + use super::error::ProposalFetchError; use super::resources; #[cfg(feature = "authorization")] @@ -155,13 +154,13 @@ mod tests { store::CircuitPredicate, }; use splinter::error::InternalError; - use splinter::rest_api::actix_web_1::AuthConfig; - use splinter::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, + use splinter_rest_api_common::auth::{ + AuthorizationHandler, AuthorizationHandlerResult, AuthorizationHeader, Identity, + IdentityProvider, }; - use splinter::rest_api::auth::identity::{Identity, IdentityProvider}; - use splinter::rest_api::auth::AuthorizationHeader; + + use crate::framework::AuthConfig; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; #[test] /// Tests a GET /admin/proposals/{circuit_id} request returns the expected proposal. diff --git a/rest_api/actix_web_1/src/admin/resources/v1/circuits.rs b/rest_api/actix_web_1/src/admin/resources/v1/circuits.rs index 26ad2cc1f3..a246324dcc 100644 --- a/rest_api/actix_web_1/src/admin/resources/v1/circuits.rs +++ b/rest_api/actix_web_1/src/admin/resources/v1/circuits.rs @@ -15,7 +15,7 @@ use std::collections::BTreeMap; use splinter::admin::store::{Circuit, Service}; -use splinter::rest_api::paging::Paging; +use splinter_rest_api_common::paging::v1::Paging; #[derive(Debug, Serialize, Clone, PartialEq)] pub(crate) struct ListCircuitsResponse<'a> { diff --git a/rest_api/actix_web_1/src/admin/resources/v1/proposals.rs b/rest_api/actix_web_1/src/admin/resources/v1/proposals.rs index 02c1f1198a..6550059e82 100644 --- a/rest_api/actix_web_1/src/admin/resources/v1/proposals.rs +++ b/rest_api/actix_web_1/src/admin/resources/v1/proposals.rs @@ -15,7 +15,7 @@ use splinter::admin::messages::{ CircuitProposal, CreateCircuit, ProposalType, SplinterNode, SplinterService, Vote, VoteRecord, }; -use splinter::rest_api::paging::Paging; +use splinter_rest_api_common::paging::v1::Paging; use crate::hex::as_hex; diff --git a/rest_api/actix_web_1/src/admin/resources/v2/circuits.rs b/rest_api/actix_web_1/src/admin/resources/v2/circuits.rs index f0635f8ad9..3043c40abe 100644 --- a/rest_api/actix_web_1/src/admin/resources/v2/circuits.rs +++ b/rest_api/actix_web_1/src/admin/resources/v2/circuits.rs @@ -15,7 +15,7 @@ use std::collections::BTreeMap; use splinter::admin::store::{Circuit, CircuitNode, CircuitStatus, Service}; -use splinter::rest_api::paging::Paging; +use splinter_rest_api_common::paging::v1::Paging; use crate::hex::to_hex; diff --git a/rest_api/actix_web_1/src/admin/resources/v2/proposals.rs b/rest_api/actix_web_1/src/admin/resources/v2/proposals.rs index b43af894a0..7902ef1f0f 100644 --- a/rest_api/actix_web_1/src/admin/resources/v2/proposals.rs +++ b/rest_api/actix_web_1/src/admin/resources/v2/proposals.rs @@ -17,7 +17,7 @@ use splinter::admin::messages::{ CircuitProposal, CircuitStatus, CreateCircuit, ProposalType, SplinterNode, SplinterService, Vote, VoteRecord, }; -use splinter::rest_api::paging::Paging; +use splinter_rest_api_common::paging::v1::Paging; use crate::hex::as_hex; use crate::hex::to_hex; diff --git a/rest_api/actix_web_1/src/admin/submit.rs b/rest_api/actix_web_1/src/admin/submit.rs index 184fb409ac..8f0a6e1471 100644 --- a/rest_api/actix_web_1/src/admin/submit.rs +++ b/rest_api/actix_web_1/src/admin/submit.rs @@ -16,10 +16,11 @@ use futures::{Future, IntoFuture}; use splinter::admin::service::{AdminCommands, AdminServiceError}; use splinter::protos::admin::CircuitManagementPayload; -use splinter::rest_api::actix_web_1::{into_protobuf, Method, ProtocolVersionRangeGuard, Resource}; use splinter::service::instance::ServiceError; use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; +use crate::framework::{into_protobuf, Method, ProtocolVersionRangeGuard, Resource}; + #[cfg(feature = "authorization")] use super::CIRCUIT_WRITE_PERMISSION; diff --git a/rest_api/actix_web_1/src/admin/ws_register_type.rs b/rest_api/actix_web_1/src/admin/ws_register_type.rs index 3645875a5e..549bb513db 100644 --- a/rest_api/actix_web_1/src/admin/ws_register_type.rs +++ b/rest_api/actix_web_1/src/admin/ws_register_type.rs @@ -27,14 +27,11 @@ use splinter::admin::service::{ }; use splinter::admin::store; use splinter::error::InvalidStateError; -use splinter::rest_api::{ - actix_web_1::{ - new_websocket_event_sender, EventSender, Method, ProtocolVersionRangeGuard, Request, - Resource, - }, - ErrorResponse, +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; + +use crate::framework::{ + new_websocket_event_sender, EventSender, Method, ProtocolVersionRangeGuard, Request, Resource, }; -use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; #[cfg(feature = "authorization")] use super::CIRCUIT_READ_PERMISSION; diff --git a/libsplinter/src/rest_api/auth/authorization/maintenance/routes/actix.rs b/rest_api/actix_web_1/src/auth/maintenance/actix.rs similarity index 94% rename from libsplinter/src/rest_api/auth/authorization/maintenance/routes/actix.rs rename to rest_api/actix_web_1/src/auth/maintenance/actix.rs index a47b337d1c..d475f2b27b 100644 --- a/libsplinter/src/rest_api/auth/authorization/maintenance/routes/actix.rs +++ b/rest_api/actix_web_1/src/auth/maintenance/actix.rs @@ -20,18 +20,20 @@ use actix_web::{web, Error, HttpRequest, HttpResponse}; use futures::{future::IntoFuture, Future}; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - auth::authorization::maintenance::MaintenanceModeAuthorizationHandler, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, +use crate::{ + auth::maintenance::MaintenanceModeAuthorizationHandler, + framework::{Method, ProtocolVersionRangeGuard, Resource}, +}; + +use splinter_rest_api_common::{ + auth::PostMaintenanceModeQuery, response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION, }; use super::{ - resources::PostMaintenanceModeQuery, AUTHORIZATION_MAINTENANCE_READ_PERMISSION, - AUTHORIZATION_MAINTENANCE_WRITE_PERMISSION, + AUTHORIZATION_MAINTENANCE_READ_PERMISSION, AUTHORIZATION_MAINTENANCE_WRITE_PERMISSION, }; -const AUTHORIZATION_MAINTENANCE_MIN: u32 = 1; +pub const AUTHORIZATION_MAINTENANCE_MIN: u32 = 1; pub fn make_maintenance_resource(auth_handler: MaintenanceModeAuthorizationHandler) -> Resource { let auth_handler1 = auth_handler.clone(); @@ -85,7 +87,7 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; - use crate::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; /// Verifies the `GET` and `POST` methods for the `/authorization/maintenance` resource work /// correctly. @@ -200,7 +202,7 @@ mod tests { // Enable (idempotent) let resp = Client::new() - .post(url.clone()) + .post(url) .query(&[("enabled", "true")]) .header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION) .send() @@ -224,7 +226,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); diff --git a/libsplinter/src/rest_api/auth/authorization/maintenance/routes/mod.rs b/rest_api/actix_web_1/src/auth/maintenance/mod.rs similarity index 64% rename from libsplinter/src/rest_api/auth/authorization/maintenance/routes/mod.rs rename to rest_api/actix_web_1/src/auth/maintenance/mod.rs index 304e74cbd4..7fd8fa09bc 100644 --- a/libsplinter/src/rest_api/auth/authorization/maintenance/routes/mod.rs +++ b/rest_api/actix_web_1/src/auth/maintenance/mod.rs @@ -14,25 +14,21 @@ //! REST API endpoints for the maintenance mode authorization handler -#[cfg(feature = "rest-api-actix-web-1")] mod actix; -#[cfg(feature = "rest-api-actix-web-1")] -mod resources; -use crate::rest_api::actix_web_1::{Resource, RestResourceProvider}; -#[cfg(feature = "rest-api-actix-web-1")] -use crate::rest_api::auth::authorization::Permission; +use crate::framework::{Resource, RestResourceProvider}; -use super::MaintenanceModeAuthorizationHandler; +use splinter_rest_api_common::auth::MaintenanceModeAuthorizationHandler; +use splinter_rest_api_common::auth::Permission; -#[cfg(feature = "rest-api-actix-web-1")] -const AUTHORIZATION_MAINTENANCE_READ_PERMISSION: Permission = Permission::Check { +pub use self::actix::{make_maintenance_resource, AUTHORIZATION_MAINTENANCE_MIN}; + +pub const AUTHORIZATION_MAINTENANCE_READ_PERMISSION: Permission = Permission::Check { permission_id: "authorization.maintenance.read", permission_display_name: "Maintenance mode read", permission_description: "Allows the client to check maintenance mode status", }; -#[cfg(feature = "rest-api-actix-web-1")] -const AUTHORIZATION_MAINTENANCE_WRITE_PERMISSION: Permission = Permission::Check { +pub const AUTHORIZATION_MAINTENANCE_WRITE_PERMISSION: Permission = Permission::Check { permission_id: "authorization.maintenance.write", permission_display_name: "Maintenance mode write", permission_description: "Allows the client to enable/disable maintenance mode", @@ -49,16 +45,7 @@ const AUTHORIZATION_MAINTENANCE_WRITE_PERMISSION: Permission = Permission::Check /// * `rest-api-actix` impl RestResourceProvider for MaintenanceModeAuthorizationHandler { fn resources(&self) -> Vec { - // Allowing unused_mut because resources must be mutable if feature rest-api-actix is - // enabled - #[allow(unused_mut)] - let mut resources = Vec::new(); - - #[cfg(feature = "rest-api-actix-web-1")] - { - resources.push(actix::make_maintenance_resource(self.clone())); - } - + let resources = vec![actix::make_maintenance_resource(self.clone())]; resources } } diff --git a/libsplinter/src/rest_api/auth/authorization/routes/actix.rs b/rest_api/actix_web_1/src/auth/make_permission_resource.rs similarity index 95% rename from libsplinter/src/rest_api/auth/authorization/routes/actix.rs rename to rest_api/actix_web_1/src/auth/make_permission_resource.rs index 5af4bb5176..4d440559aa 100644 --- a/libsplinter/src/rest_api/auth/authorization/routes/actix.rs +++ b/rest_api/actix_web_1/src/auth/make_permission_resource.rs @@ -19,13 +19,11 @@ use actix_web::HttpResponse; use futures::future::IntoFuture; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - auth::authorization::Permission, - SPLINTER_PROTOCOL_VERSION, -}; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; +use splinter_rest_api_common::{auth::Permission, SPLINTER_PROTOCOL_VERSION}; -use super::{resources::PermissionResponse, AUTHORIZATION_PERMISSIONS_READ_PERMISSION}; +use crate::auth::AUTHORIZATION_PERMISSIONS_READ_PERMISSION; +use splinter_rest_api_common::auth::PermissionResponse; const AUTHORIZATION_PERMISSIONS_MIN: u32 = 1; @@ -83,7 +81,7 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; - use crate::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; const PERM1: Permission = Permission::Check { permission_id: "id1", @@ -200,7 +198,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); diff --git a/libsplinter/src/rest_api/auth/actix/middleware.rs b/rest_api/actix_web_1/src/auth/middleware.rs similarity index 95% rename from libsplinter/src/rest_api/auth/actix/middleware.rs rename to rest_api/actix_web_1/src/auth/middleware.rs index 2ae9c49758..2eef1ef03f 100644 --- a/libsplinter/src/rest_api/auth/actix/middleware.rs +++ b/rest_api/actix_web_1/src/auth/middleware.rs @@ -21,13 +21,13 @@ use actix_web::{ Error as ActixError, HttpMessage, HttpResponse, }; use futures::{Future, IntoFuture, Poll}; +use log::debug; +use crate::framework::Method; +use splinter_rest_api_common::auth::{authorize, AuthorizationResult, IdentityProvider}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::{AuthorizationHandler, PermissionMap}; -use crate::rest_api::auth::{authorize, identity::IdentityProvider, AuthorizationResult}; -use crate::rest_api::ErrorResponse; -#[cfg(feature = "authorization")] -use crate::rest_api::Method; +use splinter_rest_api_common::auth::{AuthorizationHandler, PermissionMap}; +use splinter_rest_api_common::response_models::ErrorResponse; pub struct AuthorizationMiddleware { pub(super) identity_providers: Vec>, @@ -122,7 +122,7 @@ where } }; - match authorize( + match authorize::( #[cfg(feature = "authorization")] &method, req.path(), diff --git a/libsplinter/src/rest_api/auth/actix/mod.rs b/rest_api/actix_web_1/src/auth/mod.rs similarity index 79% rename from libsplinter/src/rest_api/auth/actix/mod.rs rename to rest_api/actix_web_1/src/auth/mod.rs index db30055951..2b5bed3ab1 100644 --- a/libsplinter/src/rest_api/auth/actix/mod.rs +++ b/rest_api/actix_web_1/src/auth/mod.rs @@ -14,32 +14,54 @@ //! Authorization middleware for the Actix REST API +#[cfg(feature = "authorization")] +mod maintenance; +#[cfg(feature = "authorization")] +mod make_permission_resource; mod middleware; +#[cfg(feature = "oauth")] +mod oauth; +#[cfg(feature = "rbac")] +mod rbac; +#[cfg(feature = "authorization")] +mod resource_provider; mod transform; +#[cfg(feature = "authorization")] +pub use maintenance::{ + make_maintenance_resource, AUTHORIZATION_MAINTENANCE_READ_PERMISSION, + AUTHORIZATION_MAINTENANCE_WRITE_PERMISSION, +}; pub use middleware::AuthorizationMiddleware; +#[cfg(feature = "oauth")] +pub use oauth::{ + callback::make_callback_route, list_users::make_oauth_list_users_resource, + login::make_login_route, logout::make_logout_route, resource_provider::OAuthResourceProvider, +}; +#[cfg(feature = "rbac")] +pub use rbac::RoleBasedAuthorizationResourceProvider; +#[cfg(feature = "authorization")] +pub use resource_provider::{ + AuthorizationResourceProvider, AUTHORIZATION_PERMISSIONS_READ_PERMISSION, +}; pub use transform::Authorization; #[cfg(test)] mod tests { use super::*; - use actix_web::dev::*; - use actix_web::{http::StatusCode, test, web, App, HttpRequest}; + use actix_web::http::Method as ActixMethod; use actix_web::{ - http::{header::HeaderValue, Method as ActixMethod}, + dev::Service, http::HeaderValue, http::StatusCode, test, web, App, HttpRequest, HttpResponse, }; - - use crate::error::InternalError; - #[cfg(feature = "authorization")] - use crate::rest_api::auth::authorization::Permission; + use splinter::error::InternalError; + use splinter_rest_api_common::auth::{AuthorizationHeader, Identity, IdentityProvider}; #[cfg(feature = "authorization")] - use crate::rest_api::auth::authorization::PermissionMap; - use crate::rest_api::auth::identity::IdentityProvider; - use crate::rest_api::auth::{identity::Identity, AuthorizationHeader}; + use splinter_rest_api_common::auth::{Permission, PermissionMap}; + #[cfg(feature = "authorization")] - use crate::rest_api::Method; + use crate::framework::Method; /// Verifies that the authorization middleware sets the `Access-Control-Allow-Credentials: true` /// header for `OPTIONS` requests. @@ -83,11 +105,11 @@ mod tests { #[cfg(feature = "authorization")] vec![], )) - .route("/", web::get().to(|| HttpResponse::Ok())); + .route("/", web::get().to(HttpResponse::Ok)); #[cfg(feature = "authorization")] let app = { - let mut permission_map = PermissionMap::::new(); + let mut permission_map = PermissionMap::default(); permission_map.add_permission(Method::Get, "/", Permission::AllowAuthenticated); app.data(permission_map) }; @@ -110,11 +132,11 @@ mod tests { #[cfg(feature = "authorization")] vec![], )) - .route("/", web::get().to(|| HttpResponse::Ok())); + .route("/", web::get().to(HttpResponse::Ok)); #[cfg(feature = "authorization")] let app = { - let mut permission_map = PermissionMap::::new(); + let mut permission_map = PermissionMap::default(); permission_map.add_permission(Method::Get, "/", Permission::AllowAuthenticated); app.data(permission_map) }; @@ -154,7 +176,7 @@ mod tests { #[cfg(feature = "authorization")] let app = { - let mut permission_map = PermissionMap::::new(); + let mut permission_map = PermissionMap::default(); permission_map.add_permission(Method::Get, "/", Permission::AllowAuthenticated); app.data(permission_map) }; diff --git a/libsplinter/src/oauth/rest_api/actix/callback.rs b/rest_api/actix_web_1/src/auth/oauth/callback.rs similarity index 90% rename from libsplinter/src/oauth/rest_api/actix/callback.rs rename to rest_api/actix_web_1/src/auth/oauth/callback.rs index d2e6853826..1b686d45ac 100644 --- a/libsplinter/src/oauth/rest_api/actix/callback.rs +++ b/rest_api/actix_web_1/src/auth/oauth/callback.rs @@ -20,26 +20,23 @@ use futures::future::IntoFuture; use rand::distributions::Alphanumeric; use rand::{thread_rng, Rng}; -use crate::biome::oauth::store::{InsertableOAuthUserSessionBuilder, OAuthUserSessionStore}; +use splinter::biome::oauth::store::{InsertableOAuthUserSessionBuilder, OAuthUserSessionStore}; #[cfg(feature = "biome-profile")] -use crate::biome::{ +use splinter::biome::{ profile::store::ProfileBuilder, profile::store::UserProfileStoreError, UserProfileStore, }; #[cfg(feature = "biome-profile")] -use crate::error::InternalError; +use splinter::error::InternalError; +use splinter::oauth::OAuthClient; +use splinter_rest_api_common::auth::{generate_redirect_query, CallbackQuery}; + +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; #[cfg(feature = "biome-profile")] -use crate::oauth::Profile as OauthProfile; -use crate::oauth::{ - rest_api::resources::callback::{generate_redirect_query, CallbackQuery}, - OAuthClient, -}; +use splinter::oauth::Profile as OauthProfile; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; const OAUTH_CALLBACK_MIN: u32 = 1; @@ -118,13 +115,13 @@ pub fn make_callback_route( user_profile_store.clone_box(), oauth_user_session_store.clone_box(), &user_info.profile().clone(), - user_info.subject.clone(), + user_info.subject().to_string(), ) { Ok(_) => debug!("User profile saved"), Err(err) => { error!( "Failed to save profile for account: {}, {}", - user_info.subject.clone(), + user_info.subject().to_string(), err ); return Box::new( @@ -339,22 +336,21 @@ mod tests { use actix::System; use actix_web::{dev::Server, web, App, HttpResponse, HttpServer}; use futures::Future; + use oauth2::basic::BasicClient; + use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use reqwest::{blocking::Client, redirect, StatusCode, Url as ReqwestUrl}; - use url::Url; - - use crate::biome::MemoryOAuthUserSessionStore; + use splinter::biome::MemoryOAuthUserSessionStore; #[cfg(feature = "biome-profile")] - use crate::biome::MemoryUserProfileStore; - use crate::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; - - use crate::oauth::{ - new_basic_client, + use splinter::biome::MemoryUserProfileStore; + use splinter::error::InvalidArgumentError; + use splinter::oauth::{ store::{InflightOAuthRequestStore, MemoryInflightOAuthRequestStore}, - tests::TestSubjectProvider, PendingAuthorization, }; + use splinter::oauth::{Profile, ProfileProvider, SubjectProvider}; + use url::Url; - use crate::oauth::tests::TestProfileProvider; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; const TOKEN_ENDPOINT: &str = "/token"; const AUTH_CODE: &str = "auth_code"; @@ -362,6 +358,62 @@ mod tests { const OAUTH_ACCESS_TOKEN: &str = "oauth_access_token"; const OAUTH_REFRESH_TOKEN: &str = "oauth_refresh_token"; + #[derive(Clone)] + pub struct TestSubjectProvider; + + impl SubjectProvider for TestSubjectProvider { + fn get_subject(&self, _: &str) -> Result, InternalError> { + Ok(Some(SUBJECT.to_string())) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + + #[derive(Clone)] + pub struct TestProfileProvider; + + impl ProfileProvider for TestProfileProvider { + fn get_profile(&self, _: &str) -> Result, InternalError> { + Ok(Some(Profile { + subject: "".to_string(), + name: None, + given_name: None, + family_name: None, + email: None, + picture: None, + })) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + + fn new_basic_client( + client_id: String, + client_secret: String, + auth_url: String, + redirect_url: String, + token_url: String, + ) -> Result { + Ok(BasicClient::new( + ClientId::new(client_id), + Some(ClientSecret::new(client_secret)), + AuthUrl::new(auth_url) + .map_err(|err| InvalidArgumentError::new("auth_url", err.to_string()))?, + Some( + TokenUrl::new(token_url) + .map_err(|err| InvalidArgumentError::new("token_url", err.to_string()))?, + ), + ) + .set_redirect_uri( + RedirectUrl::new(redirect_url) + .map_err(|err| InvalidArgumentError::new("redirect_url", err.to_string()))?, + )) + } + /// Verifies the correct functionality of the `GET /oauth/callback` endpoint when the request /// is correct /// @@ -389,10 +441,10 @@ mod tests { request_store .insert_request( csrf_token.into(), - PendingAuthorization { - pkce_verifier: "F9ZfayKQHV5exVsgM3WyzRt15UQvYxVZBm41iO-h20A".into(), - client_redirect_url: client_redirect_url.as_str().into(), - }, + PendingAuthorization::new( + "F9ZfayKQHV5exVsgM3WyzRt15UQvYxVZBm41iO-h20A".into(), + client_redirect_url.as_str().into(), + ), ) .expect("Failed to insert in-flight request"); @@ -408,7 +460,7 @@ mod tests { vec![], vec![], Box::new(TestSubjectProvider), - request_store.clone(), + request_store, Box::new(TestProfileProvider), ); @@ -577,10 +629,10 @@ mod tests { request_store .insert_request( "csrf_token".into(), - PendingAuthorization { - pkce_verifier: "F9ZfayKQHV5exVsgM3WyzRt15UQvYxVZBm41iO-h20A".into(), - client_redirect_url: "http://client/redirect".into(), - }, + PendingAuthorization::new( + "F9ZfayKQHV5exVsgM3WyzRt15UQvYxVZBm41iO-h20A".into(), + "http://client/redirect".into(), + ), ) .expect("Failed to insert in-flight request"); @@ -596,7 +648,7 @@ mod tests { vec![], vec![], Box::new(TestSubjectProvider), - request_store.clone(), + request_store, Box::new(TestProfileProvider), ); @@ -659,10 +711,10 @@ mod tests { request_store .insert_request( csrf_token.into(), - PendingAuthorization { - pkce_verifier: "F9ZfayKQHV5exVsgM3WyzRt15UQvYxVZBm41iO-h20A".into(), - client_redirect_url: "http://client/redirect".into(), - }, + PendingAuthorization::new( + "F9ZfayKQHV5exVsgM3WyzRt15UQvYxVZBm41iO-h20A".into(), + "http://client/redirect".into(), + ), ) .expect("Failed to insert in-flight request"); @@ -678,7 +730,7 @@ mod tests { vec![], vec![], Box::new(TestSubjectProvider), - request_store.clone(), + request_store, Box::new(TestProfileProvider), ); @@ -787,7 +839,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); diff --git a/libsplinter/src/oauth/rest_api/actix/list_users.rs b/rest_api/actix_web_1/src/auth/oauth/list_users.rs similarity index 88% rename from libsplinter/src/oauth/rest_api/actix/list_users.rs rename to rest_api/actix_web_1/src/auth/oauth/list_users.rs index 7bcc455bd3..6b8583966b 100644 --- a/libsplinter/src/oauth/rest_api/actix/list_users.rs +++ b/rest_api/actix_web_1/src/auth/oauth/list_users.rs @@ -12,19 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; use actix_web::{web, HttpResponse}; - -use crate::biome::oauth::store::OAuthUserSessionStore; -use crate::oauth::rest_api::{ - resources::list_users::{ListOAuthUserResponse, OAuthUserResponse, PagingQuery}, - OAUTH_USER_READ_PERMISSION, -}; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - paging::PagingBuilder, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; use futures::future::IntoFuture; +use splinter::biome::oauth::store::OAuthUserSessionStore; +#[cfg(feature = "authorization")] +use splinter_rest_api_common::auth::OAUTH_USER_READ_PERMISSION; +use splinter_rest_api_common::auth::{ListOAuthUserResponse, OAuthUserResponse, PagingQuery}; +use splinter_rest_api_common::{ + paging::v1::PagingBuilder, response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION, +}; const OAUTH_USER_READ_PROTOCOL_MIN: u32 = 1; @@ -126,12 +123,10 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; - use crate::biome::oauth::store::InsertableOAuthUserSessionBuilder; - use crate::biome::MemoryOAuthUserSessionStore; - use crate::rest_api::{ - actix_web_1::{RestApiBuilder, RestApiShutdownHandle}, - paging::Paging, - }; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; + use splinter::biome::oauth::store::InsertableOAuthUserSessionBuilder; + use splinter::biome::MemoryOAuthUserSessionStore; + use splinter_rest_api_common::paging::v1::Paging; #[derive(Deserialize)] struct TestClientOAuthUser { @@ -230,9 +225,9 @@ mod tests { let subject = format!("subject_{}", i); let oauth_access_token = format!("oauth_access_token_{}", i); let session = InsertableOAuthUserSessionBuilder::new() - .with_splinter_access_token(splinter_access_token.into()) - .with_subject(subject.into()) - .with_oauth_access_token(oauth_access_token.into()) + .with_splinter_access_token(splinter_access_token) + .with_subject(subject) + .with_oauth_access_token(oauth_access_token) .build() .expect("Unable to build session"); oauth_user_session_store @@ -267,7 +262,7 @@ mod tests { create_test_paging_response(0, 100, 100, 0, 100, 101, "/oauth/users?") ); - let next_link: String = paging.next; + let next_link: String = paging.get_next().to_string(); let url = Url::parse(&format!("http://{}{}", bind_url, next_link)).expect("Failed to parse URL"); @@ -300,7 +295,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); @@ -316,28 +311,15 @@ mod tests { fn create_test_paging_response( offset: usize, limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, + _next_offset: usize, + _previous_offset: usize, + _last_offset: usize, total: usize, link: &str, ) -> Paging { - let base_link = format!("{}limit={}&", link, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } + Paging::builder(link.to_string(), total) + .with_limit(limit) + .with_offset(offset) + .build() } } diff --git a/libsplinter/src/oauth/rest_api/actix/login.rs b/rest_api/actix_web_1/src/auth/oauth/login.rs similarity index 85% rename from libsplinter/src/oauth/rest_api/actix/login.rs rename to rest_api/actix_web_1/src/auth/oauth/login.rs index af98114d7e..45a9220ca4 100644 --- a/libsplinter/src/oauth/rest_api/actix/login.rs +++ b/rest_api/actix_web_1/src/auth/oauth/login.rs @@ -18,13 +18,12 @@ use actix_web::{http::header::LOCATION, web, HttpResponse}; use futures::future::IntoFuture; use std::collections::HashMap; -use crate::oauth::OAuthClient; +use splinter::oauth::OAuthClient; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; + +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +use splinter_rest_api_common::auth::Permission; const OAUTH_LOGIN_MIN: u32 = 1; @@ -147,19 +146,21 @@ pub fn make_login_route(client: OAuthClient) -> Resource { mod tests { use super::*; + use oauth2::basic::BasicClient; + use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use reqwest::{blocking::Client, redirect, StatusCode, Url}; - - use crate::oauth::tests::TestProfileProvider; - use crate::oauth::{ - new_basic_client, + use splinter::error::InternalError; + use splinter::error::InvalidArgumentError; + use splinter::oauth::{ store::{ InflightOAuthRequestStore, InflightOAuthRequestStoreError, MemoryInflightOAuthRequestStore, }, - tests::TestSubjectProvider, PendingAuthorization, }; - use crate::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; + use splinter::oauth::{Profile, ProfileProvider, SubjectProvider}; + + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; const CLIENT_ID: &str = "client_id"; const CLIENT_SECRET: &str = "client_secret"; @@ -167,6 +168,63 @@ mod tests { const REDIRECT_URL: &str = "http://oauth/callback"; const TOKEN_ENDPOINT: &str = "/token"; const CLIENT_REDIRECT_URL: &str = "http://client/redirect"; + const SUBJECT: &str = "subject"; + + #[derive(Clone)] + pub struct TestSubjectProvider; + + impl SubjectProvider for TestSubjectProvider { + fn get_subject(&self, _: &str) -> Result, InternalError> { + Ok(Some(SUBJECT.to_string())) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + + #[derive(Clone)] + pub struct TestProfileProvider; + + impl ProfileProvider for TestProfileProvider { + fn get_profile(&self, _: &str) -> Result, InternalError> { + Ok(Some(Profile { + subject: "".to_string(), + name: None, + given_name: None, + family_name: None, + email: None, + picture: None, + })) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + + fn new_basic_client( + client_id: String, + client_secret: String, + auth_url: String, + redirect_url: String, + token_url: String, + ) -> Result { + Ok(BasicClient::new( + ClientId::new(client_id), + Some(ClientSecret::new(client_secret)), + AuthUrl::new(auth_url) + .map_err(|err| InvalidArgumentError::new("auth_url", err.to_string()))?, + Some( + TokenUrl::new(token_url) + .map_err(|err| InvalidArgumentError::new("token_url", err.to_string()))?, + ), + ) + .set_redirect_uri( + RedirectUrl::new(redirect_url) + .map_err(|err| InvalidArgumentError::new("redirect_url", err.to_string()))?, + )) + } /// Verifies the correct functionality of the `GET /oauth/login` endpoint when the client /// redirect is specified in the request's query @@ -181,6 +239,7 @@ mod tests { /// 4. Verify the response has status `302 Found` and the `Location` header is set to the /// correct authorization URL /// 5. Shutdown the REST API + #[test] fn get_login_with_redirect_url() { let client = OAuthClient::new( @@ -354,7 +413,10 @@ mod tests { _request_id: String, authorization: PendingAuthorization, ) -> Result<(), InflightOAuthRequestStoreError> { - assert_eq!(&authorization.client_redirect_url, CLIENT_REDIRECT_URL); + assert_eq!( + &authorization.get_client_redirect_url(), + &CLIENT_REDIRECT_URL + ); Ok(()) } @@ -380,7 +442,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); diff --git a/libsplinter/src/oauth/rest_api/actix/logout.rs b/rest_api/actix_web_1/src/auth/oauth/logout.rs similarity index 94% rename from libsplinter/src/oauth/rest_api/actix/logout.rs rename to rest_api/actix_web_1/src/auth/oauth/logout.rs index 7864464141..877ed71ac2 100644 --- a/libsplinter/src/oauth/rest_api/actix/logout.rs +++ b/rest_api/actix_web_1/src/auth/oauth/logout.rs @@ -17,15 +17,17 @@ use actix_web::{HttpRequest, HttpResponse}; use futures::{future::IntoFuture, Future}; -use crate::biome::oauth::store::{OAuthUserSessionStore, OAuthUserSessionStoreError}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{ auth::{AuthorizationHeader, BearerToken}, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, + response_models::ErrorResponse, + SPLINTER_PROTOCOL_VERSION, }; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; +use splinter::biome::oauth::store::{OAuthUserSessionStore, OAuthUserSessionStoreError}; + const OAUTH_LOGOUT_MIN: u32 = 1; pub fn make_logout_route(oauth_user_session_store: Box) -> Resource { @@ -138,9 +140,9 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; - use crate::biome::oauth::store::InsertableOAuthUserSessionBuilder; - use crate::biome::MemoryOAuthUserSessionStore; - use crate::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; + use splinter::biome::oauth::store::InsertableOAuthUserSessionBuilder; + use splinter::biome::MemoryOAuthUserSessionStore; const SPLINTER_ACCESS_TOKEN: &str = "splinter_access_token"; @@ -242,7 +244,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); diff --git a/libsplinter/src/rest_api/auth/identity/oauth.rs b/rest_api/actix_web_1/src/auth/oauth/mod.rs similarity index 75% rename from libsplinter/src/rest_api/auth/identity/oauth.rs rename to rest_api/actix_web_1/src/auth/oauth/mod.rs index cc72b16501..ca0a2a1cd5 100644 --- a/libsplinter/src/rest_api/auth/identity/oauth.rs +++ b/rest_api/actix_web_1/src/auth/oauth/mod.rs @@ -12,194 +12,233 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! An identity provider backed by an OAuth server - -use std::time::Duration; - -use crate::biome::OAuthUserSessionStore; -use crate::error::InternalError; -use crate::oauth::OAuthClient; -use crate::rest_api::auth::{AuthorizationHeader, BearerToken}; - -use super::{Identity, IdentityProvider}; - -/// The default amount of time since the last authentication for which the identity provider can -/// assume the session is still valid -const DEFAULT_REAUTHENTICATION_INTERVAL: Duration = Duration::from_secs(3600); // 1 hour - -/// An identity provider, backed by an OAuth server, that returns a user's Biome ID -/// -/// This provider uses an [OAuthUserSessionStore] as a cache of identities. The session store tracks -/// all OAuth users' sessions with a "last authenticated" timestamp. Sessions are initially added by -/// the OAuth REST API endpoints when a user logs in. -/// -/// If the session has not been authenticated within the re-authentication interval, the user will -/// be re-authenticated using the internal [OAuthClient] and the session will be updated in the -/// session store. If re-authentication fails, the session will be removed from the store and the -/// user will need to start a new session by logging in. -/// -/// This identity provider will also use a session's refresh token (if it has one) to get a new -/// OAuth access token for the session as needed. -/// -/// This provider only accepts `AuthorizationHeader::Bearer(BearerToken::OAuth2(token))` -/// authorizations, and the inner token must be a valid Splinter access token for an OAuth user. -#[derive(Clone)] -pub struct OAuthUserIdentityProvider { - oauth_client: OAuthClient, - oauth_user_session_store: Box, - reauthentication_interval: Duration, -} - -impl OAuthUserIdentityProvider { - /// Creates a new OAuth user identity provider - /// - /// # Arguments - /// - /// * `oauth_client` - The OAuth client that will be used to check if a session is still valid - /// * `oauth_user_session_store` - The store that tracks users' sessions - /// * `reauthentication_interval` - The amount of time since the last authentication for which - /// the identity provider can assume the session is still valid. If this amount of time has - /// elapsed since the last authentication of a session, the session will be re-authenticated - /// by the identity provider. If not provided, the default will be used (1 hour). - pub fn new( - oauth_client: OAuthClient, - oauth_user_session_store: Box, - reauthentication_interval: Option, - ) -> Self { - Self { - oauth_client, - oauth_user_session_store, - reauthentication_interval: reauthentication_interval - .unwrap_or(DEFAULT_REAUTHENTICATION_INTERVAL), - } - } -} - -impl IdentityProvider for OAuthUserIdentityProvider { - fn get_identity( - &self, - authorization: &AuthorizationHeader, - ) -> Result, InternalError> { - let token = match authorization { - AuthorizationHeader::Bearer(BearerToken::OAuth2(token)) => token, - _ => return Ok(None), - }; - - let session = match self - .oauth_user_session_store - .get_session(token) - .map_err(|err| InternalError::from_source(err.into()))? - { - Some(session) => session, - None => return Ok(None), - }; - - let user_id = session.user().user_id().to_string(); - - let time_since_authenticated = session - .last_authenticated() - .elapsed() - .map_err(|err| InternalError::from_source(err.into()))?; - if time_since_authenticated >= self.reauthentication_interval { - match self.oauth_client.get_subject(session.oauth_access_token()) { - Ok(Some(_)) => { - let updated_session = session.into_update_builder().build(); - self.oauth_user_session_store - .update_session(updated_session) - .map_err(|err| InternalError::from_source(err.into()))?; - Ok(Some(Identity::User(user_id))) - } - Ok(None) => { - // The access token didn't work; see if there's a refresh token that can be used - // to get a new one. - match session.oauth_refresh_token() { - Some(refresh_token) => { - // Try using the session's OAuth refresh token to get a new OAuth - // access token - match self - .oauth_client - .exchange_refresh_token(refresh_token.to_string()) - { - Ok(access_token) => { - // Update the access token in the store - let updated_session = session - .into_update_builder() - .with_oauth_access_token(access_token.clone()) - .build(); - self.oauth_user_session_store - .update_session(updated_session) - .map_err(|err| InternalError::from_source(err.into()))?; - // Authenticate with the new access token; if this fails (we - // get Ok(None) or Err(_)), something's wrong that can't be - // handled here. - match self.oauth_client.get_subject(&access_token)? { - Some(_) => Ok(Some(Identity::User(user_id))), - None => Err(InternalError::with_message( - "failed to authenticate user with new access token" - .into(), - )), - } - } - Err(err) => { - // The refresh token didn't work; delete the session since it's - // no longer valid - debug!("Failed to exchange refresh token: {}", err); - self.oauth_user_session_store - .remove_session(token) - .map_err(|err| InternalError::from_source(err.into()))?; - Ok(None) - } - } - } - None => { - // The access token didn't work and there's no refresh token for this - // session; delete the session since it's no longer valid. - self.oauth_user_session_store - .remove_session(token) - .map_err(|err| InternalError::from_source(err.into()))?; - Ok(None) - } - } - } - Err(err) => { - self.oauth_user_session_store - .remove_session(token) - .map_err(|err| InternalError::from_source(err.into()))?; - Err(err) - } - } - } else { - Ok(Some(Identity::User(user_id))) - } - } - - fn clone_box(&self) -> Box { - Box::new(self.clone()) - } -} +pub(super) mod callback; +pub(super) mod list_users; +pub(super) mod login; +pub(super) mod logout; +pub(super) mod resource_provider; #[cfg(test)] mod tests { - use super::*; use std::sync::mpsc::channel; use std::thread::JoinHandle; + use std::time::Duration; use actix::System; - use actix_web::{dev::Server, web, App, HttpResponse, HttpServer}; + use actix_web::{dev::Server, web, App, HttpRequest, HttpResponse, HttpServer}; use futures::Future; - - use crate::biome::oauth::store::InsertableOAuthUserSessionBuilder; - use crate::biome::MemoryOAuthUserSessionStore; - use crate::oauth::{ + use splinter::biome::oauth::store::InsertableOAuthUserSessionBuilder; + use splinter::biome::MemoryOAuthUserSessionStore; + use splinter::biome::OAuthUserSessionStore; + use splinter::error::InternalError; + use splinter::oauth::OAuthClient; + use splinter::oauth::{ store::MemoryInflightOAuthRequestStore, OAuthClientBuilder, SubjectProvider, }; - use crate::oauth::{Profile, ProfileProvider}; + use splinter::oauth::{OpenIdProfileProvider, Profile, ProfileProvider}; + use splinter_rest_api_common::auth::{ + AuthorizationHeader, BearerToken, Identity, IdentityProvider, OAuthUserIdentityProvider, + }; + const USERINFO_ENDPOINT: &str = "/userinfo"; + const ALL_DETAILS_TOKEN: &str = "all_details"; + const ONLY_SUB_TOKEN: &str = "only_sub"; + const UNEXPECTED_RESPONSE_CODE_TOKEN: &str = "unexpected_response_code"; + const INVALID_RESPONSE_TOKEN: &str = "invalid_response"; + const SUB: &str = "sub"; + const NAME: &str = "name"; + const GIVEN_NAME: &str = "given_name"; + const FAMILY_NAME: &str = "family_name"; + const EMAIL: &str = "email"; + const PICTURE: &str = "picture"; const TOKEN_ENDPOINT: &str = "/token"; const REFRESH_TOKEN: &str = "refresh_token"; const NEW_OAUTH_ACCESS_TOKEN: &str = "new_oauth_access_token"; + /// Verifies that the OpenID profile provider correctly returns all relevant profile information + /// when it's provided. + /// + /// 1. Start the mock OpenID server + /// 2. Get the profile for a user with all details filled out + /// 3. Verify that all profile details are correct + /// 4. Shutdown the OpenID server + #[test] + fn all_details() { + let (shutdown_handle, address) = run_mock_openid_server("all_details"); + + let profile = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) + .get_profile(ALL_DETAILS_TOKEN) + .expect("Failed to get profile") + .expect("Profile not found"); + + assert_eq!(&profile.subject, SUB); + assert_eq!(profile.name.as_deref(), Some(NAME)); + assert_eq!(profile.given_name.as_deref(), Some(GIVEN_NAME)); + assert_eq!(profile.family_name.as_deref(), Some(FAMILY_NAME)); + assert_eq!(profile.email.as_deref(), Some(EMAIL)); + assert_eq!(profile.picture.as_deref(), Some(PICTURE)); + + shutdown_handle.shutdown(); + } + + /// Verifies that the OpenID profile provider correctly returns the profile when only the + /// subject is provided + /// + /// 1. Start the mock OpenID server + /// 2. Get the profile for a user with only the subject filled out + /// 3. Verify that the `subject` field is correct and all other fields are empty + /// 4. Shutdown the OpenID server + #[test] + fn only_sub() { + let (shutdown_handle, address) = run_mock_openid_server("only_sub"); + + let profile = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) + .get_profile(ONLY_SUB_TOKEN) + .expect("Failed to get profile") + .expect("Profile not found"); + + assert_eq!(&profile.subject, SUB); + assert!(profile.name.is_none()); + assert!(profile.given_name.is_none()); + assert!(profile.family_name.is_none()); + assert!(profile.email.is_none()); + assert!(profile.picture.is_none()); + + shutdown_handle.shutdown(); + } + + /// Verifies that the OpenID profile provider correctly returns `Ok(None)` when receiving a + /// `401 Unauthorized` response from the OpenID server (which means the token is unknown). + /// + /// 1. Start the mock OpenID server + /// 2. Attempt to get the profile for an unknown token + /// 3. Verify that the profile provider returns the correct value + /// 4. Shutdown the OpenID server + #[test] + fn unauthorized_token() { + let (shutdown_handle, address) = run_mock_openid_server("unauthorized_token"); + + let profile_opt = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) + .get_profile("unknown_token") + .expect("Failed to get profile"); + + assert!(profile_opt.is_none()); + + shutdown_handle.shutdown(); + } + + /// Verifies that the OpenID profile provider correctly returns an error when receiving an + /// unexpected response code from the OpenID server. + /// + /// 1. Start the mock OpenID server + /// 2. Attempt to get the profile for a token that the server will return a non-200 and non-401 + /// response for + /// 3. Verify that the profile provider returns an error + /// 4. Shutdown the OpenID server + #[test] + fn unexpected_response_code() { + let (shutdown_handle, address) = run_mock_openid_server("unauthorized_token"); + + let profile_res = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) + .get_profile(UNEXPECTED_RESPONSE_CODE_TOKEN); + + assert!(profile_res.is_err()); + + shutdown_handle.shutdown(); + } + + /// Verifies that the OpenID profile provider correctly returns an error when receiving a + /// response that doesn't contain the `sub` field. + /// + /// 1. Start the mock OpenID server + /// 2. Attempt to get the profile for a token that the server will return an invalid response + /// for + /// 3. Verify that the profile provider returns an error + /// 4. Shutdown the OpenID server + #[test] + fn invalid_response() { + let (shutdown_handle, address) = run_mock_openid_server("unauthorized_token"); + + let profile_res = OpenIdProfileProvider::new(format!("{}{}", address, USERINFO_ENDPOINT)) + .get_profile(INVALID_RESPONSE_TOKEN); + + assert!(profile_res.is_err()); + + shutdown_handle.shutdown(); + } + + /// Runs a mock OAuth OpenID server and returns its shutdown handle along with the address the + /// server is running on. + fn run_mock_openid_server(test_name: &str) -> (OpenIDServerShutdownHandle, String) { + let (tx, rx) = channel(); + + let instance_name = format!("OpenID-Server-{}", test_name); + let join_handle = std::thread::Builder::new() + .name(instance_name.clone()) + .spawn(move || { + let sys = System::new(instance_name); + let server = HttpServer::new(|| { + App::new().service(web::resource(USERINFO_ENDPOINT).to(userinfo_endpoint)) + }) + .bind("127.0.0.1:0") + .expect("Failed to bind OpenID server"); + let address = format!("http://127.0.0.1:{}", server.addrs()[0].port()); + let server = server.disable_signals().system_exit().start(); + tx.send((server, address)).expect("Failed to send server"); + sys.run().expect("OpenID server runtime failed"); + }) + .expect("Failed to spawn OpenID server thread"); + + let (server, address) = rx.recv().expect("Failed to receive server"); + + (OpenIDServerShutdownHandle(server, join_handle), address) + } + + /// The handler for the OpenID server's user info endpoint. + fn userinfo_endpoint(req: HttpRequest) -> HttpResponse { + match req + .headers() + .get("Authorization") + .and_then(|auth| auth.to_str().ok()) + .and_then(|auth_str| auth_str.strip_prefix("Bearer ")) + { + Some(token) if token == ALL_DETAILS_TOKEN => HttpResponse::Ok() + .content_type("application/json") + .json(json!({ + "sub": SUB, + "name": NAME, + "given_name": GIVEN_NAME, + "family_name": FAMILY_NAME, + "email": EMAIL, + "picture": PICTURE, + })), + Some(token) if token == ONLY_SUB_TOKEN => HttpResponse::Ok() + .content_type("application/json") + .json(json!({ + "sub": SUB, + })), + Some(token) if token == UNEXPECTED_RESPONSE_CODE_TOKEN => { + HttpResponse::BadRequest().finish() + } + Some(token) if token == INVALID_RESPONSE_TOKEN => HttpResponse::Ok().finish(), + Some(_) => HttpResponse::Unauthorized().finish(), + None => HttpResponse::BadRequest().finish(), + } + } + + struct OpenIDServerShutdownHandle(Server, JoinHandle<()>); + + impl OpenIDServerShutdownHandle { + pub fn shutdown(self) { + self.0 + .stop(false) + .wait() + .expect("Failed to stop OpenID server"); + self.1.join().expect("OpenID server thread failed"); + } + } + /// Verifies that the `OAuthUserIdentityProvider` returns a cached user identity when a session /// does not need to be re-authenticated. /// diff --git a/libsplinter/src/oauth/rest_api/resource_provider.rs b/rest_api/actix_web_1/src/auth/oauth/resource_provider.rs similarity index 85% rename from libsplinter/src/oauth/rest_api/resource_provider.rs rename to rest_api/actix_web_1/src/auth/oauth/resource_provider.rs index 5013076ce5..11d07f382d 100644 --- a/libsplinter/src/oauth/rest_api/resource_provider.rs +++ b/rest_api/actix_web_1/src/auth/oauth/resource_provider.rs @@ -12,13 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::biome::OAuthUserSessionStore; +use crate::framework::{Resource, RestResourceProvider}; +use splinter::biome::OAuthUserSessionStore; #[cfg(feature = "biome-profile")] -use crate::biome::UserProfileStore; -use crate::oauth::OAuthClient; -use crate::rest_api::actix_web_1::{Resource, RestResourceProvider}; - -use super::actix; +use splinter::biome::UserProfileStore; +use splinter::oauth::OAuthClient; /// Provides the REST API [Resource](../../../rest_api/struct.Resource.html) definitions for OAuth /// endpoints. The following endpoints are provided: @@ -59,15 +57,15 @@ impl OAuthResourceProvider { impl RestResourceProvider for OAuthResourceProvider { fn resources(&self) -> Vec { vec![ - actix::login::make_login_route(self.client.clone()), - actix::callback::make_callback_route( + super::login::make_login_route(self.client.clone()), + super::callback::make_callback_route( self.client.clone(), self.oauth_user_session_store.clone(), #[cfg(feature = "biome-profile")] self.user_profile_store.clone(), ), - actix::logout::make_logout_route(self.oauth_user_session_store.clone()), - actix::list_users::make_oauth_list_users_resource( + super::logout::make_logout_route(self.oauth_user_session_store.clone()), + super::list_users::make_oauth_list_users_resource( self.oauth_user_session_store.clone(), ), ] diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/assignments.rs b/rest_api/actix_web_1/src/auth/rbac/assignments.rs similarity index 96% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/assignments.rs rename to rest_api/actix_web_1/src/auth/rbac/assignments.rs index 15395e516f..ee8b1f10d9 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/assignments.rs +++ b/rest_api/actix_web_1/src/auth/rbac/assignments.rs @@ -17,25 +17,21 @@ use std::convert::TryInto; use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::{Future, IntoFuture, Stream}; -use crate::error::InvalidStateError; -use crate::rbac::store::{Assignment, Identity, RoleBasedAuthorizationStore}; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - auth::authorization::rbac::rest_api::{ - resources::{ - assignments::{ - AssignmentPayload, AssignmentResponse, AssignmentUpdatePayload, - ListAssignmentsResponse, - }, - PagingQuery, - }, - RBAC_READ_PERMISSION, RBAC_WRITE_PERMISSION, +use splinter::error::InvalidStateError; + +use splinter::rbac::store::{Assignment, Identity, RoleBasedAuthorizationStore}; +use splinter_rest_api_common::{ + auth::rbac::{ + AssignmentPayload, AssignmentResponse, AssignmentUpdatePayload, ListAssignmentsResponse, + PagingQuery, SendableRoleBasedAuthorizationStoreError, }, - paging::PagingBuilder, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, + auth::{RBAC_READ_PERMISSION, RBAC_WRITE_PERMISSION}, + paging::v1::PagingBuilder, + response_models::ErrorResponse, + SPLINTER_PROTOCOL_VERSION, }; -use super::error::SendableRoleBasedAuthorizationStoreError; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; const AUTHORIZATION_RBAC_ASSIGNMENTS_MIN: u32 = 1; @@ -371,15 +367,13 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; use serde_json::{to_value, Value as JsonValue}; - use crate::error::{ConstraintViolationError, ConstraintViolationType}; - use crate::rbac::store::{ + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; + use splinter::error::{ConstraintViolationError, ConstraintViolationType}; + use splinter::rbac::store::{ Assignment, AssignmentBuilder, Identity, Role, RoleBasedAuthorizationStoreError, RoleBuilder, }; - use crate::rest_api::{ - actix_web_1::{RestApiBuilder, RestApiShutdownHandle}, - paging::Paging, - }; + use splinter_rest_api_common::paging::v1::Paging; macro_rules! get_in { ($val:expr, $keys:expr, $as:ident) => {{ @@ -782,7 +776,7 @@ mod tests { assert_eq!(&Identity::User("Bob".into()), assignment.identity()); let resp = Client::new() - .post(url.clone()) + .post(url) .header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION) .json(&json!({ "identity": "Bob", @@ -1262,7 +1256,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); @@ -1278,29 +1272,16 @@ mod tests { fn create_test_paging_response( offset: usize, limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, + _next_offset: usize, + _previous_offset: usize, + _last_offset: usize, total: usize, link: &str, ) -> Paging { - let base_link = format!("{}limit={}&", link, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } + Paging::builder(link.to_string(), total) + .with_limit(limit) + .with_offset(offset) + .build() } #[derive(Clone, Default)] @@ -1378,8 +1359,8 @@ mod tests { let key = id_to_string(assignment.identity()); - if !assignments.contains_key(&key) { - assignments.insert(key, assignment); + if let std::collections::btree_map::Entry::Vacant(e) = assignments.entry(key) { + e.insert(assignment); Ok(()) } else { Err(RoleBasedAuthorizationStoreError::ConstraintViolation( @@ -1399,8 +1380,8 @@ mod tests { let key = id_to_string(assignment.identity()); - if assignments.contains_key(&key) { - assignments.insert(key, assignment); + if let std::collections::btree_map::Entry::Occupied(mut e) = assignments.entry(key) { + e.insert(assignment); Ok(()) } else { Err(RoleBasedAuthorizationStoreError::ConstraintViolation( @@ -1420,7 +1401,7 @@ mod tests { .lock() .expect("mem role based authorization store lock was poisoned"); - let key = id_to_string(&identity); + let key = id_to_string(identity); assignments.remove(&key); diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/mod.rs b/rest_api/actix_web_1/src/auth/rbac/mod.rs similarity index 93% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/mod.rs rename to rest_api/actix_web_1/src/auth/rbac/mod.rs index 3101db213c..20c1fbf20b 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/mod.rs +++ b/rest_api/actix_web_1/src/auth/rbac/mod.rs @@ -15,11 +15,11 @@ //! Actix Web 1.x RBAC REST Resource implementations. mod assignments; -mod error; mod roles; -use crate::rbac::store::RoleBasedAuthorizationStore; -use crate::rest_api::{Resource, RestResourceProvider}; +use splinter::rbac::store::RoleBasedAuthorizationStore; + +use crate::framework::{Resource, RestResourceProvider}; /// REST Resource Provider for Role-based Authorization REST resources. pub struct RoleBasedAuthorizationResourceProvider { diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/roles.rs b/rest_api/actix_web_1/src/auth/rbac/roles.rs similarity index 96% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/roles.rs rename to rest_api/actix_web_1/src/auth/rbac/roles.rs index b96165aa51..bea94ea790 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/roles.rs +++ b/rest_api/actix_web_1/src/auth/rbac/roles.rs @@ -21,22 +21,20 @@ use std::convert::TryInto; use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::{stream::Stream, Future, IntoFuture}; -use crate::error::InvalidStateError; -use crate::rbac::store::{Role, RoleBasedAuthorizationStore}; -use crate::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - auth::authorization::rbac::rest_api::{ - resources::{ - roles::{ListRoleResponse, RolePayload, RoleResponse, RoleUpdatePayload}, - PagingQuery, - }, - RBAC_READ_PERMISSION, RBAC_WRITE_PERMISSION, +use splinter::error::InvalidStateError; +use splinter::rbac::store::{Role, RoleBasedAuthorizationStore}; +use splinter_rest_api_common::{ + auth::rbac::{ + ListRoleResponse, PagingQuery, RolePayload, RoleResponse, RoleUpdatePayload, + SendableRoleBasedAuthorizationStoreError, }, - paging::PagingBuilder, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, + auth::{RBAC_READ_PERMISSION, RBAC_WRITE_PERMISSION}, + paging::v1::PagingBuilder, + response_models::ErrorResponse, + SPLINTER_PROTOCOL_VERSION, }; -use super::error::SendableRoleBasedAuthorizationStoreError; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; const AUTHORIZATION_RBAC_ROLE_MIN: u32 = 1; const AUTHORIZATION_RBAC_ROLES_MIN: u32 = 1; @@ -351,14 +349,12 @@ mod tests { use reqwest::{blocking::Client, StatusCode, Url}; use serde_json::{to_value, Value as JsonValue}; - use crate::error::{ConstraintViolationError, ConstraintViolationType}; - use crate::rbac::store::{ + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; + use splinter::error::{ConstraintViolationError, ConstraintViolationType}; + use splinter::rbac::store::{ Assignment, Identity, Role, RoleBasedAuthorizationStoreError, RoleBuilder, }; - use crate::rest_api::{ - actix_web_1::{RestApiBuilder, RestApiShutdownHandle}, - paging::Paging, - }; + use splinter_rest_api_common::paging::v1::Paging; macro_rules! get_in { ($val:expr, $keys:expr, $as:ident) => {{ @@ -644,7 +640,7 @@ mod tests { .expect("Failed to parse URL"); let resp = Client::new() - .post(url.clone()) + .post(url) .header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION) .json(&json!({ "role_id": "test-role-1", @@ -928,7 +924,7 @@ mod tests { .expect("Failed to parse URL"); let resp = Client::new() - .patch(url.clone()) + .patch(url) .header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION) .json(&json!({ "display_name": "New Test Display Name", @@ -969,7 +965,7 @@ mod tests { .expect("Failed to parse URL"); let resp = Client::new() - .patch(url.clone()) + .patch(url) .header("SplinterProtocolVersion", SPLINTER_PROTOCOL_VERSION) .json(&json!({ "permissions": [], @@ -1045,7 +1041,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .build_insecure() .expect("Failed to build REST API") .run_insecure(); @@ -1061,29 +1057,16 @@ mod tests { fn create_test_paging_response( offset: usize, limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, + _next_offset: usize, + _previous_offset: usize, + _last_offset: usize, total: usize, link: &str, ) -> Paging { - let base_link = format!("{}limit={}&", link, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } + Paging::builder(link.to_string(), total) + .with_limit(limit) + .with_offset(offset) + .build() } #[derive(Clone, Default)] diff --git a/libsplinter/src/rest_api/auth/authorization/routes/mod.rs b/rest_api/actix_web_1/src/auth/resource_provider.rs similarity index 67% rename from libsplinter/src/rest_api/auth/authorization/routes/mod.rs rename to rest_api/actix_web_1/src/auth/resource_provider.rs index ae1a479086..99ac294120 100644 --- a/libsplinter/src/rest_api/auth/authorization/routes/mod.rs +++ b/rest_api/actix_web_1/src/auth/resource_provider.rs @@ -14,17 +14,12 @@ //! REST API endpoints for authorization tools -#[cfg(feature = "rest-api-actix-web-1")] -mod actix; -#[cfg(feature = "rest-api-actix-web-1")] -mod resources; +use splinter_rest_api_common::auth::Permission; -use crate::rest_api::actix_web_1::{Resource, RestResourceProvider}; -#[cfg(feature = "rest-api-actix-web-1")] -use crate::rest_api::auth::authorization::Permission; +use crate::auth::make_permission_resource::make_permissions_resource; +use crate::framework::{Resource, RestResourceProvider}; -#[cfg(feature = "rest-api-actix-web-1")] -const AUTHORIZATION_PERMISSIONS_READ_PERMISSION: Permission = Permission::Check { +pub const AUTHORIZATION_PERMISSIONS_READ_PERMISSION: Permission = Permission::Check { permission_id: "authorization.permissions.read", permission_display_name: "Permissions read", permission_description: "Allows the client to read REST API permissions", @@ -39,17 +34,13 @@ const AUTHORIZATION_PERMISSIONS_READ_PERMISSION: Permission = Permission::Check /// /// * `rest-api-actix` pub struct AuthorizationResourceProvider { - #[cfg(feature = "rest-api-actix-web-1")] permissions: Vec, } impl AuthorizationResourceProvider { /// Creates a new `AuthorizationResourceProvider` - pub fn new(#[cfg(feature = "rest-api-actix-web-1")] permissions: Vec) -> Self { - Self { - #[cfg(feature = "rest-api-actix-web-1")] - permissions, - } + pub fn new(permissions: Vec) -> Self { + Self { permissions } } } @@ -64,14 +55,6 @@ impl RestResourceProvider for AuthorizationResourceProvider { fn resources(&self) -> Vec { // Allowing unused_mut because resources must be mutable if feature rest-api-actix is // enabled - #[allow(unused_mut)] - let mut resources = Vec::new(); - - #[cfg(feature = "rest-api-actix-web-1")] - { - resources.push(actix::make_permissions_resource(self.permissions.clone())); - } - - resources + vec![make_permissions_resource(self.permissions.clone())] } } diff --git a/libsplinter/src/rest_api/auth/actix/transform.rs b/rest_api/actix_web_1/src/auth/transform.rs similarity index 92% rename from libsplinter/src/rest_api/auth/actix/transform.rs rename to rest_api/actix_web_1/src/auth/transform.rs index 532d912e96..41737ef323 100644 --- a/libsplinter/src/rest_api/auth/actix/transform.rs +++ b/rest_api/actix_web_1/src/auth/transform.rs @@ -16,10 +16,10 @@ use actix_web::dev::*; use actix_web::Error as ActixError; use futures::future::{ok, FutureResult}; -use crate::rest_api::auth::actix::AuthorizationMiddleware; +use crate::auth::AuthorizationMiddleware; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::AuthorizationHandler; -use crate::rest_api::auth::IdentityProvider; +use splinter_rest_api_common::auth::AuthorizationHandler; +use splinter_rest_api_common::auth::IdentityProvider; /// Wrapper for the authorization middleware #[derive(Clone)] diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/authorize.rs b/rest_api/actix_web_1/src/biome/credentials/authorize.rs similarity index 91% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/authorize.rs rename to rest_api/actix_web_1/src/biome/credentials/authorize.rs index a528248255..164c81bcd3 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/authorize.rs +++ b/rest_api/actix_web_1/src/biome/credentials/authorize.rs @@ -19,10 +19,12 @@ use actix_web::HttpRequest; use jsonwebtoken::{decode, DecodingKey, Validation}; #[cfg(feature = "biome-credentials")] -use crate::biome::credentials::rest_api::resources::authorize::AuthorizationResult; -use crate::rest_api::secrets::SecretManager; +use crate::biome::credentials::resources::authorize::AuthorizationResult; +use splinter_rest_api_common::secrets::SecretManager; #[cfg(feature = "biome-credentials")] -use crate::rest_api::{actix_web_1::get_authorization_token, sessions::Claims}; +use splinter_rest_api_common::sessions::Claims; + +use crate::framework::get_authorization_token; /// Verifies the user has the correct permissions #[cfg(feature = "biome-credentials")] diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/config.rs b/rest_api/actix_web_1/src/biome/credentials/config.rs similarity index 98% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/config.rs rename to rest_api/actix_web_1/src/biome/credentials/config.rs index 2a608f49af..356ccf14d4 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/config.rs +++ b/rest_api/actix_web_1/src/biome/credentials/config.rs @@ -14,8 +14,8 @@ use std::time::Duration; -use crate::biome::credentials::store::PasswordEncryptionCost; -use crate::error::InvalidStateError; +use splinter::biome::credentials::store::PasswordEncryptionCost; +use splinter::error::InvalidStateError; const DEFAULT_ISSUER: &str = "self-issued"; const DEFAULT_DURATION: u64 = 5400; // in seconds = 90 minutes diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/login.rs b/rest_api/actix_web_1/src/biome/credentials/login.rs similarity index 96% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/login.rs rename to rest_api/actix_web_1/src/biome/credentials/login.rs index f64201b842..21bf15ec95 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/login.rs +++ b/rest_api/actix_web_1/src/biome/credentials/login.rs @@ -16,19 +16,17 @@ use std::sync::Arc; use actix_web::HttpResponse; use futures::{Future, IntoFuture}; - -use crate::biome::refresh_tokens::store::RefreshTokenStore; +use splinter::biome::credentials::store::{CredentialsStore, CredentialsStoreError}; +use splinter::biome::refresh_tokens::store::RefreshTokenStore; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::sessions::{AccessTokenIssuer, ClaimsBuilder, TokenIssuer}; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; + +use crate::framework::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}; -use crate::biome::credentials::rest_api::actix_web_1::BiomeCredentialsRestConfig; -use crate::biome::credentials::rest_api::resources::credentials::UsernamePassword; -use crate::biome::credentials::store::{CredentialsStore, CredentialsStoreError}; -use crate::rest_api::sessions::{AccessTokenIssuer, ClaimsBuilder, TokenIssuer}; +use crate::biome::credentials::resources::credentials::UsernamePassword; +use crate::biome::credentials::BiomeCredentialsRestConfig; const BIOME_LOGIN_PROTOCOL_MIN: u32 = 1; diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/logout.rs b/rest_api/actix_web_1/src/biome/credentials/logout.rs similarity index 87% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/logout.rs rename to rest_api/actix_web_1/src/biome/credentials/logout.rs index daf714c7ac..13154d2167 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/logout.rs +++ b/rest_api/actix_web_1/src/biome/credentials/logout.rs @@ -17,18 +17,14 @@ use std::sync::Arc; use actix_web::HttpResponse; use futures::IntoFuture; -use crate::biome::credentials::rest_api::{ - actix_web_1::{authorize::authorize_user, config::BiomeCredentialsRestConfig}, - resources::authorize::AuthorizationResult, -}; -use crate::biome::refresh_tokens::store::{RefreshTokenError, RefreshTokenStore}; +use crate::biome::credentials::{authorize_user, AuthorizationResult, BiomeCredentialsRestConfig}; +use crate::framework::{HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}; +use splinter::biome::refresh_tokens::store::{RefreshTokenError, RefreshTokenStore}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}, - secrets::SecretManager, - sessions::default_validation, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{ + response_models::ErrorResponse, secrets::SecretManager, sessions::default_validation, + SPLINTER_PROTOCOL_VERSION, }; const BIOME_LOGOUT_PROTOCOL_MIN: u32 = 1; diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/mod.rs b/rest_api/actix_web_1/src/biome/credentials/mod.rs similarity index 96% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/mod.rs rename to rest_api/actix_web_1/src/biome/credentials/mod.rs index 3f33b8ee76..1ec799bf1e 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/mod.rs +++ b/rest_api/actix_web_1/src/biome/credentials/mod.rs @@ -17,26 +17,35 @@ mod config; mod login; mod logout; mod register; +mod resources; mod token; mod user; mod verify; use std::sync::Arc; +use splinter::biome::refresh_tokens::store::RefreshTokenStore; + +use splinter::biome::credentials::store::CredentialsStore; #[cfg(feature = "biome-key-management")] -use crate::biome::key_management::store::KeyStore; -use crate::biome::{ - credentials::store::CredentialsStore, refresh_tokens::store::RefreshTokenStore, -}; -use crate::error::InvalidStateError; -use crate::rest_api::{ - auth::identity::biome::BiomeUserIdentityProvider, +use splinter::biome::key_management::store::KeyStore; +use splinter::error::InvalidStateError; +use splinter_rest_api_common::{ + auth::BiomeUserIdentityProvider, secrets::{AutoSecretManager, SecretManager}, sessions::{default_validation, AccessTokenIssuer}, - Resource, RestResourceProvider, }; +use crate::framework::{Resource, RestResourceProvider}; + +pub(crate) use authorize::authorize_user; pub use config::{BiomeCredentialsRestConfig, BiomeCredentialsRestConfigBuilder}; +pub use resources::authorize::AuthorizationResult; +pub use resources::credentials::UsernamePassword; +#[cfg(feature = "biome-key-management")] +pub use resources::key_management::ResponseKey; +pub use resources::token::RefreshToken; +pub use resources::user::ModifyUser; /// Provides the following REST API endpoints for Biome credentials: /// @@ -266,16 +275,15 @@ mod tests { use reqwest::blocking::Client; + use crate::framework::{AuthConfig, RestApiBuilder, RestApiShutdownHandle}; #[cfg(feature = "biome-key-management")] - use crate::biome::MemoryKeyStore; - use crate::biome::{MemoryCredentialsStore, MemoryRefreshTokenStore}; + use splinter::biome::MemoryKeyStore; + use splinter::biome::{MemoryCredentialsStore, MemoryRefreshTokenStore}; #[cfg(feature = "authorization")] - use crate::error::InternalError; - use crate::rest_api::actix_web_1::{AuthConfig, RestApiBuilder, RestApiShutdownHandle}; + use splinter::error::InternalError; #[cfg(feature = "authorization")] - use crate::rest_api::auth::{ - authorization::{AuthorizationHandler, AuthorizationHandlerResult}, - identity::Identity, + use splinter_rest_api_common::auth::{ + AuthorizationHandler, AuthorizationHandlerResult, Identity, }; #[derive(Serialize)] diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/register.rs b/rest_api/actix_web_1/src/biome/credentials/register.rs similarity index 95% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/register.rs rename to rest_api/actix_web_1/src/biome/credentials/register.rs index d58dc23973..28fa997785 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/register.rs +++ b/rest_api/actix_web_1/src/biome/credentials/register.rs @@ -13,22 +13,21 @@ // limitations under the License. use std::sync::Arc; -use uuid::Uuid; use actix_web::HttpResponse; use futures::{Future, IntoFuture}; +use uuid::Uuid; -use crate::biome::credentials::rest_api::actix_web_1::BiomeCredentialsRestConfig; -use crate::biome::credentials::rest_api::resources::credentials::{NewUser, UsernamePassword}; -use crate::biome::credentials::store::{ +use crate::framework::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}; +use splinter::biome::credentials::store::{ CredentialsBuilder, CredentialsStore, CredentialsStoreError, }; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; + +use crate::biome::credentials::resources::credentials::{NewUser, UsernamePassword}; +use crate::biome::credentials::BiomeCredentialsRestConfig; /// This is the UUID namespace for Biome user IDs generated for users that register with Biome /// credentials. This will prevent collisions with Biome user IDs generated for users that login diff --git a/libsplinter/src/biome/credentials/rest_api/resources/authorize.rs b/rest_api/actix_web_1/src/biome/credentials/resources/authorize.rs similarity index 89% rename from libsplinter/src/biome/credentials/rest_api/resources/authorize.rs rename to rest_api/actix_web_1/src/biome/credentials/resources/authorize.rs index 772a860dc3..a15b42cb76 100644 --- a/libsplinter/src/biome/credentials/rest_api/resources/authorize.rs +++ b/rest_api/actix_web_1/src/biome/credentials/resources/authorize.rs @@ -14,9 +14,9 @@ //! Defines results from user authorization. -use crate::rest_api::sessions::Claims; +use splinter_rest_api_common::sessions::Claims; -pub(crate) enum AuthorizationResult { +pub enum AuthorizationResult { Authorized(Claims), Unauthorized, Failed, diff --git a/libsplinter/src/biome/credentials/rest_api/resources/credentials.rs b/rest_api/actix_web_1/src/biome/credentials/resources/credentials.rs similarity index 92% rename from libsplinter/src/biome/credentials/rest_api/resources/credentials.rs rename to rest_api/actix_web_1/src/biome/credentials/resources/credentials.rs index f44a60f98b..8fba46240e 100644 --- a/libsplinter/src/biome/credentials/rest_api/resources/credentials.rs +++ b/rest_api/actix_web_1/src/biome/credentials/resources/credentials.rs @@ -15,13 +15,13 @@ //! Defines credentials used to register and authenticate users. #[derive(Deserialize)] -pub(crate) struct UsernamePassword { +pub struct UsernamePassword { pub username: String, pub hashed_password: String, } #[derive(Serialize)] -pub(crate) struct NewUser<'a> { +pub struct NewUser<'a> { pub user_id: &'a str, pub username: &'a str, } diff --git a/libsplinter/src/biome/credentials/rest_api/resources/key_management.rs b/rest_api/actix_web_1/src/biome/credentials/resources/key_management.rs similarity index 92% rename from libsplinter/src/biome/credentials/rest_api/resources/key_management.rs rename to rest_api/actix_web_1/src/biome/credentials/resources/key_management.rs index e7d4448468..286e350d4d 100644 --- a/libsplinter/src/biome/credentials/rest_api/resources/key_management.rs +++ b/rest_api/actix_web_1/src/biome/credentials/resources/key_management.rs @@ -14,17 +14,17 @@ //! Defines structures used in key management. -use crate::biome::key_management::Key; +use splinter::biome::key_management::Key; #[derive(Deserialize)] -pub(crate) struct NewKey { +pub struct NewKey { pub public_key: String, pub encrypted_private_key: String, pub display_name: String, } #[derive(Serialize)] -pub(crate) struct ResponseKey<'a> { +pub struct ResponseKey<'a> { public_key: &'a str, user_id: &'a str, display_name: &'a str, diff --git a/libsplinter/src/biome/credentials/rest_api/resources/mod.rs b/rest_api/actix_web_1/src/biome/credentials/resources/mod.rs similarity index 100% rename from libsplinter/src/biome/credentials/rest_api/resources/mod.rs rename to rest_api/actix_web_1/src/biome/credentials/resources/mod.rs diff --git a/libsplinter/src/biome/credentials/rest_api/resources/token.rs b/rest_api/actix_web_1/src/biome/credentials/resources/token.rs similarity index 100% rename from libsplinter/src/biome/credentials/rest_api/resources/token.rs rename to rest_api/actix_web_1/src/biome/credentials/resources/token.rs diff --git a/libsplinter/src/biome/credentials/rest_api/resources/user.rs b/rest_api/actix_web_1/src/biome/credentials/resources/user.rs similarity index 96% rename from libsplinter/src/biome/credentials/rest_api/resources/user.rs rename to rest_api/actix_web_1/src/biome/credentials/resources/user.rs index 3c40242259..8d3d396cec 100644 --- a/libsplinter/src/biome/credentials/rest_api/resources/user.rs +++ b/rest_api/actix_web_1/src/biome/credentials/resources/user.rs @@ -16,7 +16,7 @@ use super::key_management::NewKey; #[derive(Deserialize)] -pub(crate) struct ModifyUser { +pub struct ModifyUser { pub username: String, pub hashed_password: String, pub new_password: Option, diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/token.rs b/rest_api/actix_web_1/src/biome/credentials/token.rs similarity index 95% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/token.rs rename to rest_api/actix_web_1/src/biome/credentials/token.rs index 223d3ec503..536ec1cc8d 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/token.rs +++ b/rest_api/actix_web_1/src/biome/credentials/token.rs @@ -14,30 +14,29 @@ use std::sync::Arc; -use actix_web::HttpResponse; -use futures::{Future, IntoFuture}; - -use crate::biome::{ - credentials::rest_api::{ - actix_web_1::{ - authorize::{authorize_user, validate_claims}, - config::BiomeCredentialsRestConfig, - }, - resources::{authorize::AuthorizationResult, token::RefreshToken}, - }, - refresh_tokens::store::{RefreshTokenError, RefreshTokenStore}, -}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::secrets::SecretManager; -use crate::rest_api::{ - actix_web_1::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}, +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::secrets::SecretManager; +use splinter_rest_api_common::{ + response_models::ErrorResponse, sessions::{ default_validation, ignore_exp_validation, AccessTokenIssuer, ClaimsBuilder, TokenIssuer, }, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, + SPLINTER_PROTOCOL_VERSION, }; +use crate::biome::credentials::{ + resources::{authorize::AuthorizationResult, token::RefreshToken}, + { + authorize::{authorize_user, validate_claims}, + config::BiomeCredentialsRestConfig, + }, +}; +use crate::framework::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}; +use actix_web::HttpResponse; +use futures::{Future, IntoFuture}; +use splinter::biome::refresh_tokens::store::{RefreshTokenError, RefreshTokenStore}; + const BIOME_TOKEN_PROTOCOL_MIN: u32 = 1; /// Defines a REST endpoint for requesting a new authorization token diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/user.rs b/rest_api/actix_web_1/src/biome/credentials/user.rs similarity index 89% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/user.rs rename to rest_api/actix_web_1/src/biome/credentials/user.rs index 1a1fc0af45..39ff769b11 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/user.rs +++ b/rest_api/actix_web_1/src/biome/credentials/user.rs @@ -15,33 +15,50 @@ use std::sync::Arc; use actix_web::HttpResponse; -use futures::{Future, IntoFuture}; +#[cfg(feature = "biome-key-management")] +use futures::Future; +use futures::IntoFuture; -use crate::biome::credentials::rest_api::actix_web_1::config::BiomeCredentialsRestConfig; -use crate::biome::credentials::store::{CredentialsStore, CredentialsStoreError}; -use crate::rest_api::{ - actix_web_1::{into_bytes, HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +#[cfg(feature = "biome-key-management")] +use crate::biome::credentials::BiomeCredentialsRestConfig; +#[cfg(feature = "biome-key-management")] +use crate::framework::into_bytes; +#[cfg(feature = "biome-key-management")] +use crate::framework::HandlerFunction; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; +use splinter::biome::credentials::store::CredentialsStore; +#[cfg(feature = "biome-key-management")] +use splinter::biome::credentials::store::CredentialsStoreError; +#[cfg(feature = "authorization")] +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; #[cfg(feature = "biome-key-management")] -use crate::biome::key_management::{ +use splinter::biome::key_management::{ store::{KeyStore, KeyStoreError}, Key, }; #[cfg(feature = "biome-key-management")] -use crate::biome::credentials::rest_api::resources::{ - key_management::ResponseKey, user::ModifyUser, -}; -#[cfg(feature = "authorization")] -use crate::biome::credentials::rest_api::{ - BIOME_USER_READ_PERMISSION, BIOME_USER_WRITE_PERMISSION, -}; +use crate::biome::credentials::resources::{key_management::ResponseKey, user::ModifyUser}; const BIOME_LIST_USERS_PROTOCOL_MIN: u32 = 1; +#[cfg(feature = "biome-key-management")] const BIOME_USER_PROTOCOL_MIN: u32 = 1; +#[cfg(feature = "authorization")] +const BIOME_USER_READ_PERMISSION: Permission = Permission::Check { + permission_id: "biome.user.read", + permission_display_name: "Biome user read", + permission_description: "Allows the client to view all Biome users", +}; +#[cfg(all(feature = "authorization", feature = "biome-key-management"))] +const BIOME_USER_WRITE_PERMISSION: Permission = Permission::Check { + permission_id: "biome.user.write", + permission_display_name: "Biome user write", + permission_description: "Allows the client to modify all Biome users", +}; + /// Defines a REST endpoint to list users from the db pub fn make_list_route(credentials_store: Arc) -> Resource { let resource = Resource::build("/biome/users").add_request_guard( @@ -123,6 +140,7 @@ pub fn make_user_routes( } } +#[cfg(feature = "biome-key-management")] /// Defines a REST endpoint to fetch a user from the database /// returns the user's ID and username fn add_fetch_user_method(credentials_store: Arc) -> HandlerFunction { @@ -299,6 +317,7 @@ fn add_modify_user_method( }) } +#[cfg(feature = "biome-key-management")] /// Defines a REST endpoint to delete a user from the database fn add_delete_user_method(credentials_store: Arc) -> HandlerFunction { Box::new(move |request, _| { diff --git a/libsplinter/src/biome/credentials/rest_api/actix_web_1/verify.rs b/rest_api/actix_web_1/src/biome/credentials/verify.rs similarity index 94% rename from libsplinter/src/biome/credentials/rest_api/actix_web_1/verify.rs rename to rest_api/actix_web_1/src/biome/credentials/verify.rs index 90074022b1..442b88d54a 100644 --- a/libsplinter/src/biome/credentials/rest_api/actix_web_1/verify.rs +++ b/rest_api/actix_web_1/src/biome/credentials/verify.rs @@ -16,22 +16,21 @@ use std::sync::Arc; use actix_web::HttpResponse; use futures::{Future, IntoFuture}; +use splinter_rest_api_common::{ + response_models::ErrorResponse, secrets::SecretManager, sessions::default_validation, + SPLINTER_PROTOCOL_VERSION, +}; +use crate::biome::credentials::resources::authorize::AuthorizationResult; +use crate::framework::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{into_bytes, Method, ProtocolVersionRangeGuard, Resource}, - secrets::SecretManager, - sessions::default_validation, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +use splinter_rest_api_common::auth::Permission; -use crate::biome::credentials::rest_api::actix_web_1::config::BiomeCredentialsRestConfig; -use crate::biome::credentials::store::{CredentialsStore, CredentialsStoreError}; +use crate::biome::credentials::BiomeCredentialsRestConfig; +use splinter::biome::credentials::store::{CredentialsStore, CredentialsStoreError}; -use super::super::resources::authorize::AuthorizationResult; -use super::super::resources::credentials::UsernamePassword; use super::authorize::authorize_user; +use crate::biome::credentials::UsernamePassword; const BIOME_VERIFY_PROTOCOL_MIN: u32 = 1; diff --git a/rest_api/actix_web_1/src/biome/key_management/endpoints.rs b/rest_api/actix_web_1/src/biome/key_management/endpoints.rs index 1dc66aa843..fde06fd3a1 100644 --- a/rest_api/actix_web_1/src/biome/key_management/endpoints.rs +++ b/rest_api/actix_web_1/src/biome/key_management/endpoints.rs @@ -16,18 +16,15 @@ use std::sync::Arc; use crate::biome::key_management::resources::{NewKey, ResponseKey, UpdatedKey}; +use crate::framework::{into_bytes, HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}; use actix_web::HttpResponse; use futures::{Future, IntoFuture}; use splinter::biome::key_management::store::{KeyStore, KeyStoreError}; use splinter::biome::key_management::Key; +use splinter_rest_api_common::auth::Identity; #[cfg(feature = "authorization")] -use splinter::rest_api::auth::authorization::Permission; -use splinter::rest_api::{ - actix_web_1::{into_bytes, HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}, - auth::identity::Identity, - ErrorResponse, -}; -use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; const BIOME_KEYS_PROTOCOL_MIN: u32 = 1; const BIOME_REPLACE_KEYS_PROTOCOL_MIN: u32 = 2; diff --git a/rest_api/actix_web_1/src/biome/key_management/mod.rs b/rest_api/actix_web_1/src/biome/key_management/mod.rs index 9583484ee1..d189237983 100644 --- a/rest_api/actix_web_1/src/biome/key_management/mod.rs +++ b/rest_api/actix_web_1/src/biome/key_management/mod.rs @@ -18,7 +18,8 @@ mod resources; use std::sync::Arc; use splinter::biome::key_management::store::KeyStore; -use splinter::rest_api::{Resource, RestResourceProvider}; + +use crate::framework::{Resource, RestResourceProvider}; /// Provides the following REST API endpoints for Biome key management: /// @@ -58,20 +59,18 @@ mod tests { use reqwest::blocking::Client; - use crate::biome::{ - credentials::rest_api::{ - BiomeCredentialsRestConfigBuilder, BiomeCredentialsRestResourceProviderBuilder, - }, - MemoryCredentialsStore, MemoryKeyStore, MemoryRefreshTokenStore, - }; + use splinter::biome::{MemoryCredentialsStore, MemoryKeyStore, MemoryRefreshTokenStore}; #[cfg(feature = "authorization")] - use crate::error::InternalError; - use crate::rest_api::actix_web_1::{AuthConfig, RestApiBuilder, RestApiShutdownHandle}; + use splinter::error::InternalError; #[cfg(feature = "authorization")] - use crate::rest_api::auth::{ - authorization::{AuthorizationHandler, AuthorizationHandlerResult}, - identity::Identity, + use splinter_rest_api_common::auth::{ + AuthorizationHandler, AuthorizationHandlerResult, Identity, + }; + + use crate::biome::credentials::{ + BiomeCredentialsRestConfigBuilder, BiomeCredentialsRestResourceProviderBuilder, }; + use crate::framework::{AuthConfig, RestApiBuilder, RestApiShutdownHandle}; #[derive(Serialize)] struct UsernamePassword { diff --git a/rest_api/actix_web_1/src/biome/mod.rs b/rest_api/actix_web_1/src/biome/mod.rs index 3a7bf0c86a..9962fd7a3b 100644 --- a/rest_api/actix_web_1/src/biome/mod.rs +++ b/rest_api/actix_web_1/src/biome/mod.rs @@ -12,5 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "biome-credentials")] +pub mod credentials; #[cfg(feature = "biome-key-management")] pub mod key_management; +#[cfg(feature = "biome-profile")] +pub mod profile; diff --git a/libsplinter/src/biome/profile/rest_api/actix_web_1/mod.rs b/rest_api/actix_web_1/src/biome/profile/mod.rs similarity index 72% rename from libsplinter/src/biome/profile/rest_api/actix_web_1/mod.rs rename to rest_api/actix_web_1/src/biome/profile/mod.rs index 317eeae722..8c9d3b4310 100644 --- a/libsplinter/src/biome/profile/rest_api/actix_web_1/mod.rs +++ b/rest_api/actix_web_1/src/biome/profile/mod.rs @@ -12,14 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod profile; mod profiles; mod profiles_identity; +mod route; use std::sync::Arc; -use crate::biome::profile::store::UserProfileStore; -use crate::rest_api::{Resource, RestResourceProvider}; +use splinter::biome::profile::store::UserProfileStore; + +use crate::framework::{Resource, RestResourceProvider}; +#[cfg(feature = "authorization")] +use splinter_rest_api_common::auth::Permission; + +#[cfg(feature = "authorization")] +const BIOME_PROFILE_READ_PERMISSION: Permission = Permission::Check { + permission_id: "biome.profile.read", + permission_display_name: "Biome profile read", + permission_description: "Allows the client to view all Biome user profiles", +}; /// Provides the following REST API endpoints for Biome profiles: /// @@ -41,7 +51,7 @@ impl RestResourceProvider for BiomeProfileRestResourceProvider { vec![ profiles::make_profiles_list_route(self.profile_store.clone()), profiles_identity::make_profiles_routes(self.profile_store.clone()), - profile::make_profile_route(self.profile_store.clone()), + route::make_profile_route(self.profile_store.clone()), ] } } diff --git a/libsplinter/src/biome/profile/rest_api/actix_web_1/profiles.rs b/rest_api/actix_web_1/src/biome/profile/profiles.rs similarity index 90% rename from libsplinter/src/biome/profile/rest_api/actix_web_1/profiles.rs rename to rest_api/actix_web_1/src/biome/profile/profiles.rs index f24bf3ca63..cd4a1e28fe 100644 --- a/libsplinter/src/biome/profile/rest_api/actix_web_1/profiles.rs +++ b/rest_api/actix_web_1/src/biome/profile/profiles.rs @@ -15,14 +15,14 @@ use std::sync::Arc; use actix_web::HttpResponse; -use futures::IntoFuture; #[cfg(feature = "authorization")] -use crate::biome::profile::rest_api::BIOME_PROFILE_READ_PERMISSION; -use crate::biome::profile::store::UserProfileStore; -use crate::rest_api::{ - ErrorResponse, Method, ProtocolVersionRangeGuard, Resource, SPLINTER_PROTOCOL_VERSION, -}; +use super::BIOME_PROFILE_READ_PERMISSION; + +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; +use futures::IntoFuture; +use splinter::biome::profile::store::UserProfileStore; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; const BIOME_LIST_PROFILES_PROTOCOL_MIN: u32 = 1; diff --git a/libsplinter/src/biome/profile/rest_api/actix_web_1/profiles_identity.rs b/rest_api/actix_web_1/src/biome/profile/profiles_identity.rs similarity index 90% rename from libsplinter/src/biome/profile/rest_api/actix_web_1/profiles_identity.rs rename to rest_api/actix_web_1/src/biome/profile/profiles_identity.rs index dfc14ff1f9..b54931f7a9 100644 --- a/libsplinter/src/biome/profile/rest_api/actix_web_1/profiles_identity.rs +++ b/rest_api/actix_web_1/src/biome/profile/profiles_identity.rs @@ -15,15 +15,14 @@ use std::sync::Arc; use actix_web::HttpResponse; -use futures::IntoFuture; #[cfg(feature = "authorization")] -use crate::biome::profile::rest_api::BIOME_PROFILE_READ_PERMISSION; -use crate::biome::profile::store::{UserProfileStore, UserProfileStoreError}; -use crate::rest_api::{ - ErrorResponse, HandlerFunction, Method, ProtocolVersionRangeGuard, Resource, - SPLINTER_PROTOCOL_VERSION, -}; +use super::BIOME_PROFILE_READ_PERMISSION; + +use crate::framework::{HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}; +use futures::IntoFuture; +use splinter::biome::profile::store::{UserProfileStore, UserProfileStoreError}; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; const BIOME_FETCH_PROFILES_PROTOCOL_MIN: u32 = 1; diff --git a/libsplinter/src/biome/profile/rest_api/actix_web_1/profile.rs b/rest_api/actix_web_1/src/biome/profile/route.rs similarity index 88% rename from libsplinter/src/biome/profile/rest_api/actix_web_1/profile.rs rename to rest_api/actix_web_1/src/biome/profile/route.rs index 3de7e29a15..e320d5b682 100644 --- a/libsplinter/src/biome/profile/rest_api/actix_web_1/profile.rs +++ b/rest_api/actix_web_1/src/biome/profile/route.rs @@ -17,14 +17,12 @@ use std::sync::Arc; use actix_web::HttpResponse; use futures::IntoFuture; -use crate::biome::profile::store::UserProfileStore; +use crate::framework::{HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}; +use splinter::biome::profile::store::UserProfileStore; +use splinter_rest_api_common::auth::Identity; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; -use crate::rest_api::{ - actix_web_1::{HandlerFunction, Method, ProtocolVersionRangeGuard, Resource}, - auth::identity::Identity, - ErrorResponse, SPLINTER_PROTOCOL_VERSION, -}; +use splinter_rest_api_common::auth::Permission; +use splinter_rest_api_common::{response_models::ErrorResponse, SPLINTER_PROTOCOL_VERSION}; const BIOME_FETCH_PROFILE_PROTOCOL_MIN: u32 = 1; diff --git a/libsplinter/src/rest_api/cors.rs b/rest_api/actix_web_1/src/cors.rs similarity index 100% rename from libsplinter/src/rest_api/cors.rs rename to rest_api/actix_web_1/src/cors.rs diff --git a/libsplinter/src/service/instance/factory/endpoint.rs b/rest_api/actix_web_1/src/endpoint_factory.rs similarity index 100% rename from libsplinter/src/service/instance/factory/endpoint.rs rename to rest_api/actix_web_1/src/endpoint_factory.rs diff --git a/rest_api/actix_web_1/src/framework/actix_web_1/resource/builder.rs b/rest_api/actix_web_1/src/framework/actix_web_1/resource/builder.rs new file mode 100644 index 0000000000..bc2515d2e4 --- /dev/null +++ b/rest_api/actix_web_1/src/framework/actix_web_1/resource/builder.rs @@ -0,0 +1,25 @@ +// Copyright 2018-2022 Cargill Incorporated +// +// 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. + +use std::sync::Arc; + +use crate::rest_api::RequestGuard; + +use super::Method; + +pub struct ResourceBuilder { + route: String, + request_guards: Vec>, + methods: Vec, +} diff --git a/rest_api/actix_web_1/src/framework/actix_web_1/resource/resource_method.rs b/rest_api/actix_web_1/src/framework/actix_web_1/resource/resource_method.rs new file mode 100644 index 0000000000..465d02359f --- /dev/null +++ b/rest_api/actix_web_1/src/framework/actix_web_1/resource/resource_method.rs @@ -0,0 +1,101 @@ +// Copyright 2018-2022 Cargill Incorporated +// +// 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. + +use std::sync::Arc; + +use crate::error::InternalError; +use crate::rest_api::auth::authorization::Permission; + +use super::HandlerFunction; +use super::Method; + +#[derive(Clone)] +pub(super) struct ResourceMethod { + method: Method, + permission: Permission, + handler: Arc, +} + +impl ResourceMethod { + pub fn new(method: Method, permission: Permission, handler: Arc) -> Self { + Self { + method, + permission, + handler, + } + } + + pub fn builder() -> ResourceMethodBuilder { + ResourceMethodBui lder::default() + } + + pub fn method(&self) -> Method { + self.method + } + + #[cfg(feature = "authorization")] + pub fn permission(&self) -> Permission { + self.permission + } + + pub fn handler(&self) -> Arc { + self.handler.clone() + } +} + +#[derive(Default)] +pub(super) struct ResourceMethodBuilder { + method: Option, + #[cfg(feature = "authorization")] + permission: Option, + handler: Option>, +} + +impl ResourceMethodBuilder { + pub(super) fn with_method(mut self, method: Method) -> Self { + self.method = Some(method); + self + } + + pub(super) fn with_handler(mut self, handler: Arc) -> Self { + self.handler = Some(handler); + self + } + + #[cfg(feature = "authorization")] + pub(super) fn with_permission(mut self, permission: Permission) -> Self { + self.permission = Some(permission); + self + } + + pub(super) fn build(self) -> Result { + let method = self + .method + .ok_or_else(|| InternalError::with_message("Method must be specified".to_string()))?; + let handler = self + .handler + .ok_or_else(|| InternalError::with_message("Handler must be specified".to_string()))?; + #[cfg(feature = "authorization")] + let permission = self.permission.ok_or_else(|| { + InternalError::with_message("Permission must be specified".to_string()) + })?; + + Ok(ResourceMethod { + method, + handler, + #[cfg(feature = "authorization")] + permission, + }) + } +} diff --git a/libsplinter/src/rest_api/actix_web_1/api.rs b/rest_api/actix_web_1/src/framework/api.rs similarity index 96% rename from libsplinter/src/rest_api/actix_web_1/api.rs rename to rest_api/actix_web_1/src/framework/api.rs index 05ba9d3db2..ef4df64511 100644 --- a/libsplinter/src/rest_api/actix_web_1/api.rs +++ b/rest_api/actix_web_1/src/framework/api.rs @@ -17,15 +17,18 @@ use std::thread; use actix_web::{middleware, App, HttpServer}; use futures::Future; +use log::{debug, error, info}; -#[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::{ - routes::AuthorizationResourceProvider, AuthorizationHandler, PermissionMap, -}; -use crate::rest_api::auth::{actix::Authorization, identity::IdentityProvider}; +use crate::auth::Authorization; #[cfg(feature = "rest-api-cors")] -use crate::rest_api::cors::Cors; -use crate::rest_api::{BindConfig, RestApiServerError}; +use crate::cors::Cors; +use splinter_rest_api_common::auth::IdentityProvider; +#[cfg(feature = "authorization")] +use splinter_rest_api_common::auth::{AuthorizationHandler, PermissionMap}; +use splinter_rest_api_common::{bind_config::BindConfig, error::RestApiServerError}; + +#[cfg(feature = "authorization")] +use crate::auth::AuthorizationResourceProvider; use super::Resource; #[cfg(feature = "authorization")] diff --git a/libsplinter/src/rest_api/actix_web_1/auth.rs b/rest_api/actix_web_1/src/framework/auth.rs similarity index 86% rename from libsplinter/src/rest_api/actix_web_1/auth.rs rename to rest_api/actix_web_1/src/framework/auth.rs index 38e60ed0e9..6be6332d03 100644 --- a/libsplinter/src/rest_api/actix_web_1/auth.rs +++ b/rest_api/actix_web_1/src/framework/auth.rs @@ -12,19 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "biome-credentials")] use actix_web::HttpRequest; #[cfg(feature = "cylinder-jwt")] use cylinder::Verifier; #[cfg(feature = "biome-credentials")] -use crate::biome::credentials::rest_api::BiomeCredentialsRestResourceProvider; +use crate::biome::credentials::BiomeCredentialsRestResourceProvider; #[cfg(feature = "oauth")] -use crate::biome::OAuthUserSessionStore; +use splinter::biome::oauth::store::OAuthUserSessionStore; #[cfg(all(feature = "oauth", feature = "biome-profile"))] -use crate::biome::UserProfileStore; +use splinter::biome::UserProfileStore; +use splinter_rest_api_common::auth::IdentityProvider; +#[cfg(feature = "biome-credentials")] +use splinter_rest_api_common::error::RequestError; #[cfg(feature = "oauth")] -use crate::rest_api::OAuthConfig; -use crate::rest_api::{auth::identity::IdentityProvider, RequestError}; +use splinter_rest_api_common::oauth_config::OAuthConfig; use super::Resource; @@ -64,6 +67,7 @@ pub enum AuthConfig { }, } +#[cfg(feature = "biome-credentials")] pub fn require_header(header_key: &str, request: &HttpRequest) -> Result { let header = request.headers().get(header_key).ok_or_else(|| { RequestError::MissingHeader(format!("Header {} not included in Request", header_key)) @@ -74,6 +78,7 @@ pub fn require_header(header_key: &str, request: &HttpRequest) -> Result Result { let auth_header = require_header("Authorization", request)?; Ok(auth_header diff --git a/libsplinter/src/rest_api/actix_web_1/builder.rs b/rest_api/actix_web_1/src/framework/builder.rs similarity index 95% rename from libsplinter/src/rest_api/actix_web_1/builder.rs rename to rest_api/actix_web_1/src/framework/builder.rs index c793c430c7..5940591006 100644 --- a/libsplinter/src/rest_api/actix_web_1/builder.rs +++ b/rest_api/actix_web_1/src/framework/builder.rs @@ -17,23 +17,25 @@ use std::sync::Arc; #[cfg(feature = "cylinder-jwt")] use std::sync::Mutex; -use crate::error::InvalidStateError; +use splinter::error::InvalidStateError; #[cfg(feature = "oauth")] -use crate::oauth::{GithubOAuthClientBuilder, OpenIdOAuthClientBuilder}; +use splinter::oauth::{GithubOAuthClientBuilder, OpenIdOAuthClientBuilder}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::AuthorizationHandler; +use splinter_rest_api_common::auth::AuthorizationHandler; #[cfg(feature = "cylinder-jwt")] -use crate::rest_api::auth::identity::cylinder::CylinderKeyIdentityProvider; -#[cfg(feature = "oauth")] -use crate::rest_api::{ - auth::identity::oauth::OAuthUserIdentityProvider, OAuthConfig, OAuthResourceProvider, +use splinter_rest_api_common::auth::CylinderKeyIdentityProvider; +use splinter_rest_api_common::{ + auth::IdentityProvider, bind_config::BindConfig, error::RestApiServerError, }; -use crate::rest_api::{auth::identity::IdentityProvider, BindConfig, RestApiServerError}; +#[cfg(feature = "oauth")] +use splinter_rest_api_common::{auth::OAuthUserIdentityProvider, oauth_config::OAuthConfig}; -use super::AuthConfig; +use super::auth::AuthConfig; #[cfg(any(feature = "biome-credentials", feature = "oauth"))] use super::RestResourceProvider; use super::{Resource, RestApi}; +#[cfg(feature = "oauth")] +use crate::auth::OAuthResourceProvider; /// Builder `struct` for `RestApi`. #[derive(Default)] @@ -269,8 +271,8 @@ impl RestApiBuilder { mod test { use super::*; - use crate::error::InternalError; - use crate::rest_api::auth::{identity::Identity, AuthorizationHeader}; + use splinter::error::InternalError; + use splinter_rest_api_common::auth::{AuthorizationHeader, Identity}; /// Verifies that the `RestApiBuilder` builds successfully when all required configuration is /// provided. diff --git a/libsplinter/src/rest_api/actix_web_1/error.rs b/rest_api/actix_web_1/src/framework/error.rs similarity index 100% rename from libsplinter/src/rest_api/actix_web_1/error.rs rename to rest_api/actix_web_1/src/framework/error.rs diff --git a/libsplinter/src/rest_api/actix_web_1/guard.rs b/rest_api/actix_web_1/src/framework/guard.rs similarity index 99% rename from libsplinter/src/rest_api/actix_web_1/guard.rs rename to rest_api/actix_web_1/src/framework/guard.rs index 7504c04718..604f0ee058 100644 --- a/libsplinter/src/rest_api/actix_web_1/guard.rs +++ b/rest_api/actix_web_1/src/framework/guard.rs @@ -15,6 +15,7 @@ use super::Method; use actix_web::{Error as ActixError, HttpRequest, HttpResponse}; use futures::{Future, IntoFuture}; +use serde_json::json; /// A continuation indicates whether or not a guard should allow a given request to continue, or to /// return a result. diff --git a/libsplinter/src/rest_api/actix_web_1/mod.rs b/rest_api/actix_web_1/src/framework/mod.rs similarity index 81% rename from libsplinter/src/rest_api/actix_web_1/mod.rs rename to rest_api/actix_web_1/src/framework/mod.rs index a206ee20a3..e630e2d8a5 100644 --- a/libsplinter/src/rest_api/actix_web_1/mod.rs +++ b/rest_api/actix_web_1/src/framework/mod.rs @@ -18,14 +18,20 @@ mod builder; mod error; mod guard; mod resource; +#[cfg(feature = "websocket")] mod websocket; pub use api::{RestApi, RestApiShutdownHandle}; -pub use auth::{get_authorization_token, require_header, AuthConfig}; +#[cfg(feature = "authorization")] +pub use auth::AuthConfig; +#[cfg(all(feature = "authorization", feature = "biome-credentials"))] +pub use auth::{get_authorization_token, require_header}; + pub use builder::RestApiBuilder; pub use error::ResponseError; pub use guard::{Continuation, ProtocolVersionRangeGuard, RequestGuard}; pub use resource::{ into_bytes, into_protobuf, HandlerFunction, Method, Resource, RestResourceProvider, }; +#[cfg(feature = "websocket")] pub use websocket::{new_websocket_event_sender, EventSender, Request, Response}; diff --git a/libsplinter/src/rest_api/actix_web_1/resource.rs b/rest_api/actix_web_1/src/framework/resource.rs similarity index 96% rename from libsplinter/src/rest_api/actix_web_1/resource.rs rename to rest_api/actix_web_1/src/framework/resource.rs index b7914782ee..6241caa023 100644 --- a/libsplinter/src/rest_api/actix_web_1/resource.rs +++ b/rest_api/actix_web_1/src/framework/resource.rs @@ -21,10 +21,11 @@ use actix_web::{ }; use futures::{future::IntoFuture, stream::Stream, Future}; use protobuf::{self, Message}; +use serde_json::json; use std::cmp::PartialEq; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::{Permission, PermissionMap}; +use splinter_rest_api_common::auth::{Permission, PermissionMap}; use super::{Continuation, RequestGuard}; @@ -112,8 +113,11 @@ struct ResourceMethod { /// ``` /// use actix_web::HttpResponse; /// use futures::IntoFuture; -/// use splinter::rest_api::{Resource, Method, auth::authorization::Permission}; +/// use splinter_rest_api_common::auth::Permission; /// +/// use splinter_rest_api_actix_web_1::framework::{Resource, Method}; +/// +/// #[cfg(feature = "authorization")] /// Resource::build("/index") /// .add_method(Method::Get, Permission::AllowUnauthenticated, |r, p| { /// Box::new( @@ -200,8 +204,10 @@ impl Resource { /// ``` /// use actix_web::{HttpRequest, HttpResponse}; /// use futures::IntoFuture; - /// use splinter::rest_api::{Resource, Method, Continuation, auth::authorization::Permission}; + /// use splinter_rest_api_actix_web_1::framework::{Resource, Method, Continuation}; + /// use splinter_rest_api_common::auth::Permission; /// + /// #[cfg(feature = "authorization")] /// Resource::build("/index") /// .add_request_guard(|r: &HttpRequest| { /// if !r.headers().contains_key("GuardFlag") { diff --git a/libsplinter/src/rest_api/actix_web_1/websocket.rs b/rest_api/actix_web_1/src/framework/websocket.rs similarity index 100% rename from libsplinter/src/rest_api/actix_web_1/websocket.rs rename to rest_api/actix_web_1/src/framework/websocket.rs diff --git a/rest_api/actix_web_1/src/lib.rs b/rest_api/actix_web_1/src/lib.rs index d22902ccd3..e15e39098a 100644 --- a/rest_api/actix_web_1/src/lib.rs +++ b/rest_api/actix_web_1/src/lib.rs @@ -24,8 +24,12 @@ extern crate serde_json; #[cfg(feature = "admin-service")] pub mod admin; +pub mod auth; #[cfg(feature = "biome")] pub mod biome; +#[cfg(feature = "rest-api-cors")] +pub mod cors; +pub mod framework; pub mod open_api; #[cfg(feature = "registry")] pub mod registry; diff --git a/rest_api/actix_web_1/src/open_api/resource_provider.rs b/rest_api/actix_web_1/src/open_api/resource_provider.rs index 38f98b9e95..08e11c1e63 100644 --- a/rest_api/actix_web_1/src/open_api/resource_provider.rs +++ b/rest_api/actix_web_1/src/open_api/resource_provider.rs @@ -14,14 +14,15 @@ // limitations under the License. #[cfg(feature = "authorization")] -use splinter::rest_api::auth::authorization::Permission; -use splinter::rest_api::{Method, Resource, RestResourceProvider}; +use splinter_rest_api_common::auth::Permission; + +use crate::framework::{Method, Resource, RestResourceProvider}; #[derive(Default)] pub struct OpenApiResourceProvider {} impl RestResourceProvider for OpenApiResourceProvider { - fn resources(&self) -> Vec { + fn resources(&self) -> Vec { #[cfg(feature = "authorization")] { vec![Resource::build(".openapi.yaml").add_method( diff --git a/rest_api/actix_web_1/src/registry/mod.rs b/rest_api/actix_web_1/src/registry/mod.rs index 32a8980625..15ecc4d1d6 100644 --- a/rest_api/actix_web_1/src/registry/mod.rs +++ b/rest_api/actix_web_1/src/registry/mod.rs @@ -19,9 +19,9 @@ mod nodes; mod nodes_identity; mod resources; -use splinter::rest_api::actix_web_1::{Resource, RestResourceProvider}; +use crate::framework::{Resource, RestResourceProvider}; #[cfg(feature = "authorization")] -use splinter::rest_api::auth::authorization::Permission; +use splinter_rest_api_common::auth::Permission; use splinter::registry::RwRegistry; @@ -65,3 +65,930 @@ impl RestResourceProvider for RwRegistryRestResourceProvider { self.resources.clone() } } + +#[cfg(feature = "registry-remote")] +#[cfg(test)] +mod tests { + use super::*; + + use std::fs::File; + + use actix_web::HttpResponse; + use futures::future::IntoFuture; + use tempfile::{Builder, TempDir}; + + use splinter::registry::RemoteYamlRegistry; + #[cfg(feature = "authorization")] + use splinter_rest_api_common::auth::Permission; + + use crate::framework::{Method, Resource, RestApiBuilder, RestApiShutdownHandle}; + + /// Verifies that a remote file that contains two nodes with the same identity is rejected (not + /// loaded). + #[test] + fn duplicate_identity() { + let mut registry = mock_registry(); + registry[0].identity = "identity".into(); + registry[1].identity = "identity".into(); + let test_config = TestConfig::setup("duplicate_identity", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains two nodes with the same endpoint is rejected (not + /// loaded). + #[test] + fn duplicate_endpoint() { + let mut registry = mock_registry(); + registry[0].endpoints = vec!["endpoint".into()]; + registry[1].endpoints = vec!["endpoint".into()]; + let test_config = TestConfig::setup("duplicate_endpoint", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains a node with an empty string as its identity is + /// rejected (not loaded). + #[test] + fn empty_identity() { + let mut registry = mock_registry(); + registry[0].identity = "".into(); + let test_config = TestConfig::setup("empty_identity", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains a node with an empty string in its endpoints is + /// rejected (not loaded). + #[test] + fn empty_endpoint() { + let mut registry = mock_registry(); + registry[0].endpoints = vec!["".into()]; + let test_config = TestConfig::setup("empty_endpoint", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains a node with an empty string as its display name is + /// rejected (not loaded). + #[test] + fn empty_display_name() { + let mut registry = mock_registry(); + registry[0].display_name = "".into(); + let test_config = TestConfig::setup("empty_display_name", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains a node with an empty string in its keys is + /// rejected (not loaded). + #[test] + fn empty_key() { + let mut registry = mock_registry(); + registry[0].keys = vec!["".into()]; + let test_config = TestConfig::setup("empty_key", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains a node with no endpoints is rejected (not loaded). + #[test] + fn missing_endpoints() { + let mut registry = mock_registry(); + registry[0].endpoints = vec![]; + let test_config = TestConfig::setup("missing_endpoints", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that a remote file that contains a node with no keys is rejected (not loaded). + #[test] + fn missing_keys() { + let mut registry = mock_registry(); + registry[0].keys = vec![]; + let test_config = TestConfig::setup("missing_keys", Some(registry)); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `fetch_node` with an existing identity returns the correct node. + #[test] + fn fetch_node_ok() { + let test_config = TestConfig::setup("fetch_node_ok", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let expected_node = mock_registry().pop().expect("Failed to get expected node"); + let node = remote_registry + .get_node(&expected_node.identity) + .expect("Failed to fetch node") + .expect("Node not found"); + assert_eq!(node, expected_node); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `fetch_node` with a non-existent identity returns Ok(None) + #[test] + fn fetch_node_not_found() { + let test_config = TestConfig::setup("fetch_node_not_found", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + assert!(remote_registry + .get_node("NodeNotInRegistry") + .expect("Failed to fetch node") + .is_none()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// + /// Verifies that `has_node` properly determines if a node exists in the registry. + /// + #[test] + fn has_node() { + let test_config = TestConfig::setup("has_node", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let expected_node = mock_registry().pop().expect("Failed to get expected node"); + assert!(remote_registry + .has_node(&expected_node.identity) + .expect("Failed to check if expected_node exists")); + assert!(!remote_registry + .has_node("NodeNotInRegistry") + .expect("Failed to check for non-existent node")); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `list_nodes` returns all nodes in the remote file. + #[test] + fn list_nodes() { + let test_config = TestConfig::setup("list_nodes", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let nodes = remote_registry + .list_nodes(&[]) + .expect("Failed to retrieve nodes") + .collect::>(); + + assert_eq!(nodes.len(), mock_registry().len()); + for node in mock_registry() { + assert!(nodes.contains(&node)); + } + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `list_nodes` returns an empty list when there are no nodes in the remote file. + #[test] + fn list_nodes_empty() { + let test_config = TestConfig::setup("list_nodes_empty", Some(vec![])); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let nodes = remote_registry + .list_nodes(&[]) + .expect("Failed to retrieve nodes") + .collect::>(); + + assert!(nodes.is_empty()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `list_nodes` returns the correct nodes when a metadata filter is provided. + #[test] + fn list_nodes_filter_metadata() { + let test_config = TestConfig::setup("list_nodes_filter_metadata", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let filter = vec![MetadataPredicate::Eq( + "company".into(), + mock_registry()[0] + .metadata + .get("company") + .expect("company metadata not set") + .into(), + )]; + + let nodes = remote_registry + .list_nodes(&filter) + .expect("Failed to retrieve nodes") + .collect::>(); + + assert_eq!(nodes.len(), 1); + assert_eq!(nodes[0], mock_registry()[0]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `list_nodes` returns the correct nodes when multiple metadata filters are + /// provided. + #[test] + fn list_nodes_filter_multiple() { + let test_config = TestConfig::setup("list_nodes_filter_multiple", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let filter = vec![ + MetadataPredicate::Eq( + "company".to_string(), + mock_registry()[2] + .metadata + .get("company") + .unwrap() + .to_string(), + ), + MetadataPredicate::Eq( + "admin".to_string(), + mock_registry()[2] + .metadata + .get("admin") + .unwrap() + .to_string(), + ), + ]; + + let nodes = remote_registry + .list_nodes(&filter) + .expect("Failed to retrieve nodes") + .collect::>(); + + assert_eq!(nodes.len(), 1); + assert_eq!(nodes[0], mock_registry()[2]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that `list_nodes` returns an empty list when no nodes fit the filtering criteria. + #[test] + fn list_nodes_filter_empty() { + let test_config = TestConfig::setup("list_nodes_filter_empty", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let filter = vec![MetadataPredicate::Eq( + "admin".to_string(), + "not an admin".to_string(), + )]; + + let nodes = remote_registry + .list_nodes(&filter) + .expect("Failed to retrieve nodes") + .collect::>(); + + assert!(nodes.is_empty()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that when the remote file is available at startup, it's fetched and cached + /// successfully. The internal list of nodes and the backing file should match the remote file. + #[test] + fn file_available_at_startup() { + let test_config = TestConfig::setup("file_available_at_startup", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that when the remote file is not available at startup, the registry starts up with + /// an empty cache. When the remote file becomes available, it should be fetched and cached on + /// the next read. + #[test] + fn file_unavailable_at_startup() { + // Start without a remote file + let test_config = TestConfig::setup("file_unavailable_at_startup", None); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + // Verify that the registry is still empty + verify_internal_cache(&test_config, &remote_registry, vec![]); + + // Make the remote file available now + test_config.update_registry(Some(mock_registry())); + + // Verify that the registry's contents were updated + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that when auto refresh is turned off, the auto refresh thread is not running. + #[test] + fn auto_refresh_disabled() { + let test_config = TestConfig::setup("auto_refresh_disabled", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + + // The `running` atomic bool is only set if the auto refresh thread was started. + assert!(shutdown_handle.running.is_none()); + + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that when auto refresh is turned on, the auto refresh thread is running and + /// refreshes the registry in the background + #[test] + fn auto_refresh_enabled() { + let test_config = TestConfig::setup("auto_refresh_enabled", Some(mock_registry())); + + let refresh_period = Duration::from_secs(1); + let mut remote_registry = RemoteYamlRegistry::new( + test_config.url(), + test_config.path(), + Some(refresh_period), + None, + ) + .expect("Failed to create registry"); + + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + + // The `running` atomic bool is only set if the auto refresh thread was started. + assert!(shutdown_handle.running.is_some()); + + test_config.update_registry(Some(vec![])); + + // Wait twice as long as the auto refresh period to be sure it has a chance to refresh + std::thread::sleep(refresh_period * 2); + + // Verify that the registry's contents were updated + verify_internal_cache(&test_config, &remote_registry, vec![]); + + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that when forced refresh feature is disabled, the registry is not refreshed on + /// read. + #[test] + fn forced_refresh_disabled() { + let test_config = TestConfig::setup("forced_refresh_disabled", Some(mock_registry())); + + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + test_config.update_registry(Some(vec![])); + + // Verify that the registry's contents are the same as before, even though the remote file + // was updated + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that when forced refresh is turned on, the registry refreshes on read after the + /// refresh period has elapsed. + #[test] + fn forced_refresh_enabled() { + let test_config = TestConfig::setup("forced_refresh_enabled", Some(mock_registry())); + + let refresh_period = Duration::from_millis(10); + let mut remote_registry = RemoteYamlRegistry::new( + test_config.url(), + test_config.path(), + None, + Some(refresh_period), + ) + .expect("Failed to create registry"); + + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + test_config.update_registry(Some(vec![])); + + // Wait at least as long as the forced refresh period + std::thread::sleep(refresh_period); + + // Verify that the registry's contents are updated on read + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that any changes made to the remote file are fetched on restart if the remote file + /// is available. + #[test] + fn restart_file_available() { + let test_config = TestConfig::setup("restart_file_available", Some(mock_registry())); + + // Start the registry the first time, verify its contents, and shut it down + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + + // Update the remote file + test_config.update_registry(Some(vec![])); + + // Start the registry again and verify that it has the updated registry contents + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + verify_internal_cache(&test_config, &remote_registry, vec![]); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + /// Verifies that if the remote file is not available when the registry restarts, the old + /// contents will still be available. + #[test] + fn restart_file_unavailable() { + let test_config = TestConfig::setup("restart_file_unavailable", Some(mock_registry())); + + // Start the registry the first time, verify its contents, and shut it down + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + + // Make the remote file unavailable + test_config.update_registry(None); + + // Start the registry again and verify that the old contents are still available + let mut remote_registry = + RemoteYamlRegistry::new(test_config.url(), test_config.path(), None, None) + .expect("Failed to create registry"); + verify_internal_cache(&test_config, &remote_registry, mock_registry()); + + let mut shutdown_handle = remote_registry + .take_shutdown_handle() + .expect("Unable to get shutdown handle"); + shutdown_handle.signal_shutdown(); + shutdown_handle + .wait_for_shutdown() + .expect("Unable to shutdown remote registry"); + test_config.shutdown(); + } + + // Restart, remote file not available + + /// Creates a mock registry. + fn mock_registry() -> Vec { + vec![ + Node::builder("Node-123") + .with_endpoint("tcps://12.0.0.123:8431") + .with_display_name("Bitwise IO - Node 1") + .with_key("abcd") + .with_metadata("company", "Bitwise IO") + .with_metadata("admin", "Bob") + .build() + .expect("Failed to build node1"), + Node::builder("Node-456") + .with_endpoint("tcps://12.0.0.123:8434") + .with_display_name("Cargill - Node 1") + .with_key("0123") + .with_metadata("company", "Cargill") + .with_metadata("admin", "Carol") + .build() + .expect("Failed to build node2"), + Node::builder("Node-789") + .with_endpoint("tcps://12.0.0.123:8435") + .with_display_name("Cargill - Node 2") + .with_key("4567") + .with_metadata("company", "Cargill") + .with_metadata("admin", "Charlie") + .build() + .expect("Failed to build node3"), + ] + } + + /// Verifies that the retrieved nodes and the backing file of the `remote_registry` match the + /// contents of the `expected_registry`. + fn verify_internal_cache( + test_config: &TestConfig, + remote_registry: &RemoteYamlRegistry, + expected_registry: Vec, + ) { + // Verify the internal list of nodes + assert_eq!( + remote_registry.get_nodes().expect("Failed to get nodes"), + expected_registry, + ); + + // Verify the backing file's contents + let filename = compute_cache_filename(test_config.url(), test_config.path()) + .expect("Failed to compute cache filename"); + let file = File::open(filename).expect("Failed to open cache file"); + let file_contents: Vec = + serde_yaml::from_reader(file).expect("Failed to deserialize cache file"); + + let file_contents_nodes: Vec = file_contents + .into_iter() + .map(|node| Node::try_from(node).expect("Unable to build node")) + .collect(); + assert_eq!(file_contents_nodes, expected_registry); + } + + /// Simplifies tests by handling some of the setup and tear down. + struct TestConfig { + _temp_dir: TempDir, + temp_dir_path: String, + registry: Arc>>>, + registry_url: String, + rest_api_shutdown_handle: RestApiShutdownHandle, + rest_api_join_handle: std::thread::JoinHandle<()>, + } + + impl TestConfig { + /// Setup for the test, using the `test_name` as the prefix for the temp directory and the + /// `registry` to populate the remote file (if `Some`, otherwise the remote file won't be + /// available). + fn setup(test_name: &str, registry: Option>) -> Self { + let temp_dir = Builder::new() + .prefix(test_name) + .tempdir() + .expect("Failed to create temp dir"); + let temp_dir_path = temp_dir + .path() + .to_str() + .expect("Failed to get path") + .to_string(); + + let registry = Arc::new(Mutex::new(registry)); + + let (rest_api_shutdown_handle, rest_api_join_handle, registry_url) = + serve_registry(registry.clone()); + + Self { + _temp_dir: temp_dir, + temp_dir_path, + registry, + registry_url, + rest_api_shutdown_handle, + rest_api_join_handle, + } + } + + /// Gets the temp directory's path + fn path(&self) -> &str { + &self.temp_dir_path + } + + /// Gets the URL for the registry file + fn url(&self) -> &str { + &self.registry_url + } + + /// Updates the `registry` file served up by the REST API; if `registry` is `None`, the + /// remote file won't be available. + fn update_registry(&self, registry: Option>) { + *self.registry.lock().expect("Registry lock poisonsed") = registry; + } + + /// Shuts down the REST API; this should be called at the end of every test that uses + /// `TestConfig`. + fn shutdown(self) { + self.rest_api_shutdown_handle + .shutdown() + .expect("Unable to shutdown rest api"); + self.rest_api_join_handle + .join() + .expect("Unable to join rest api thread"); + } + } + + /// Wraps `run_rest_api_on_open_port`, serving up the given `registry` as a registry YAML file + /// that can be fetched at the returned URL. If `registry` is `None`, the registry file will not + /// be available. + fn serve_registry( + registry: Arc>>>, + ) -> (RestApiShutdownHandle, std::thread::JoinHandle<()>, String) { + let mut resource = Resource::build("/registry.yaml"); + #[cfg(feature = "authorization")] + { + resource = resource.add_method( + Method::Get, + Permission::AllowUnauthenticated, + move |_, _| { + Box::new(match &*registry.lock().expect("Registry lock poisoned") { + Some(registry) => { + let yaml_registry: Vec = registry + .iter() + .map(|node| YamlNode::from(node.clone())) + .collect(); + HttpResponse::Ok() + .body( + serde_yaml::to_vec(&yaml_registry) + .expect("Failed to serialize registry file"), + ) + .into_future() + } + None => HttpResponse::NotFound().finish().into_future(), + }) + }, + ) + } + #[cfg(not(feature = "authorization"))] + { + resource = resource.add_method(Method::Get, move |_, _| { + Box::new(match &*registry.lock().expect("Registry lock poisoned") { + Some(registry) => { + let yaml_registry: Vec = registry + .iter() + .map(|node| YamlNode::from(node.clone())) + .collect(); + HttpResponse::Ok() + .body( + serde_yaml::to_vec(&yaml_registry) + .expect("Failed to serialize registry file"), + ) + .into_future() + } + None => HttpResponse::NotFound().finish().into_future(), + }) + }) + } + let (shutdown, join, url) = run_rest_api_on_open_port(vec![resource]); + + (shutdown, join, format!("http://{}/registry.yaml", url)) + } + + fn run_rest_api_on_open_port( + resources: Vec, + ) -> (RestApiShutdownHandle, std::thread::JoinHandle<()>, String) { + #[cfg(not(feature = "https-bind"))] + let bind = "127.0.0.1:0"; + #[cfg(feature = "https-bind")] + let bind = crate::rest_api::BindConfig::Http("127.0.0.1:0".into()); + + let result = RestApiBuilder::new() + .with_bind(bind) + .add_resources(resources.clone()) + .build_insecure() + .expect("Failed to build REST API") + .run_insecure(); + match result { + Ok((shutdown_handle, join_handle)) => { + let port = shutdown_handle.port_numbers()[0]; + (shutdown_handle, join_handle, format!("127.0.0.1:{}", port)) + } + Err(err) => panic!("Failed to run REST API: {}", err), + } + } +} diff --git a/rest_api/actix_web_1/src/registry/nodes.rs b/rest_api/actix_web_1/src/registry/nodes.rs index 751fd8a748..e708ce3513 100644 --- a/rest_api/actix_web_1/src/registry/nodes.rs +++ b/rest_api/actix_web_1/src/registry/nodes.rs @@ -25,11 +25,13 @@ use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::{future::IntoFuture, stream::Stream, Future}; use splinter::error::InvalidStateError; use splinter::registry::{MetadataPredicate, Node, RegistryReader, RegistryWriter, RwRegistry}; -use splinter::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - paging::{PagingBuilder, DEFAULT_LIMIT, DEFAULT_OFFSET}, - percent_encode_filter_query, ErrorResponse, +use splinter_rest_api_common::{ + paging::v1::{PagingBuilder, DEFAULT_LIMIT, DEFAULT_OFFSET}, + percent_encode_filter_query::percent_encode_filter_query, + response_models::ErrorResponse, }; + +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; use super::error::RegistryRestApiError; @@ -303,16 +305,14 @@ mod tests { use splinter::error::InternalError; use splinter::error::InvalidStateError; use splinter::registry::{NodeIter, RegistryError}; - use splinter::rest_api::actix_web_1::AuthConfig; - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, - }; - use splinter::rest_api::auth::identity::{Identity, IdentityProvider}; - use splinter::rest_api::auth::AuthorizationHeader; - use splinter::rest_api::{ - actix_web_1::{RestApiBuilder, RestApiShutdownHandle}, - paging::Paging, + use splinter_rest_api_common::auth::{ + AuthorizationHandler, AuthorizationHandlerResult, AuthorizationHeader, Identity, + IdentityProvider, }; + use splinter_rest_api_common::paging::v1::Paging; + + use crate::framework::AuthConfig; + use crate::framework::{RestApiBuilder, RestApiShutdownHandle}; #[test] /// Tests a GET /registry/nodes request with no filters returns the expected nodes. @@ -499,7 +499,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .push_auth_config(auth_config) .with_authorization_handlers(authorization_handlers) .build() @@ -517,29 +517,16 @@ mod tests { fn create_test_paging_response( offset: usize, limit: usize, - next_offset: usize, - previous_offset: usize, - last_offset: usize, + _next_offset: usize, + _previous_offset: usize, + _last_offset: usize, total: usize, link: &str, ) -> Paging { - let base_link = format!("{}limit={}&", link, limit); - let current_link = format!("{}offset={}", base_link, offset); - let first_link = format!("{}offset=0", base_link); - let next_link = format!("{}offset={}", base_link, next_offset); - let previous_link = format!("{}offset={}", base_link, previous_offset); - let last_link = format!("{}offset={}", base_link, last_offset); - - Paging { - current: current_link, - offset, - limit, - total, - first: first_link, - prev: previous_link, - next: next_link, - last: last_link, - } + Paging::builder(link.to_string(), total) + .with_limit(limit) + .with_offset(offset) + .build() } fn get_node_1() -> Node { @@ -625,15 +612,17 @@ mod tests { self.nodes .lock() .expect("mem registry lock was poisoned") - .insert(node.identity().to_string().clone(), node); + .insert(node.identity().to_string(), node); Ok(()) } fn update_node(&self, node: Node) -> Result<(), RegistryError> { let mut inner = self.nodes.lock().expect("mem registry lock was poisoned"); - if inner.contains_key(&node.identity().to_string()) { - inner.insert(node.identity().to_string(), node); + if let std::collections::hash_map::Entry::Occupied(mut e) = + inner.entry(node.identity().to_string()) + { + e.insert(node); Ok(()) } else { Err(RegistryError::InvalidStateError( diff --git a/rest_api/actix_web_1/src/registry/nodes_identity.rs b/rest_api/actix_web_1/src/registry/nodes_identity.rs index 279a28a52d..f4c8d95785 100644 --- a/rest_api/actix_web_1/src/registry/nodes_identity.rs +++ b/rest_api/actix_web_1/src/registry/nodes_identity.rs @@ -23,12 +23,10 @@ use std::convert::TryFrom; use actix_web::{error::BlockingError, web, Error, HttpRequest, HttpResponse}; use futures::{future::IntoFuture, stream::Stream, Future}; +use crate::framework::{Method, ProtocolVersionRangeGuard, Resource}; use splinter::error::InvalidStateError; use splinter::registry::{Node, RegistryReader, RegistryWriter, RwRegistry}; -use splinter::rest_api::{ - actix_web_1::{Method, ProtocolVersionRangeGuard, Resource}, - ErrorResponse, -}; +use splinter_rest_api_common::response_models::ErrorResponse; use splinter_rest_api_common::SPLINTER_PROTOCOL_VERSION; use super::error::RegistryRestApiError; @@ -216,13 +214,10 @@ mod tests { use splinter::error::InternalError; use splinter::error::InvalidStateError; use splinter::registry::{MetadataPredicate, NodeIter, RegistryError}; - use splinter::rest_api::actix_web_1::AuthConfig; - use splinter::rest_api::actix_web_1::{RestApiBuilder, RestApiShutdownHandle}; - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, - }; - use splinter::rest_api::auth::identity::{Identity, IdentityProvider}; - use splinter::rest_api::auth::AuthorizationHeader; + use splinter_rest_api_common::auth::{AuthorizationHandler, AuthorizationHandlerResult}; + use splinter_rest_api_common::auth::{AuthorizationHeader, Identity, IdentityProvider}; + + use crate::framework::{AuthConfig, RestApiBuilder, RestApiShutdownHandle}; #[test] /// Tests a GET /registry/nodes/{identity} request returns the expected node. @@ -425,7 +420,7 @@ mod tests { let result = RestApiBuilder::new() .with_bind(bind) - .add_resources(resources.clone()) + .add_resources(resources) .push_auth_config(auth_config) .with_authorization_handlers(authorization_handlers) .build() diff --git a/rest_api/actix_web_1/src/registry/resources/nodes.rs b/rest_api/actix_web_1/src/registry/resources/nodes.rs index 0148c4b9d4..3790dbfa96 100644 --- a/rest_api/actix_web_1/src/registry/resources/nodes.rs +++ b/rest_api/actix_web_1/src/registry/resources/nodes.rs @@ -17,7 +17,7 @@ use std::convert::TryFrom; use serde::{Deserialize, Serialize}; use splinter::registry::{InvalidNodeError, Node}; -use splinter::rest_api::paging::Paging; +use splinter_rest_api_common::paging::v1::Paging; #[derive(Debug, Clone, PartialEq, Serialize)] pub struct ListNodesResponse<'a> { diff --git a/rest_api/actix_web_1/src/scabbard/batch_statuses.rs b/rest_api/actix_web_1/src/scabbard/batch_statuses.rs index f923fae89b..ef32c930ff 100644 --- a/rest_api/actix_web_1/src/scabbard/batch_statuses.rs +++ b/rest_api/actix_web_1/src/scabbard/batch_statuses.rs @@ -18,10 +18,8 @@ use std::time::Duration; use actix_web::{web, HttpResponse}; use futures::IntoFuture; -use splinter::{ - rest_api::{ErrorResponse, Method, ProtocolVersionRangeGuard}, - service::rest_api::ServiceEndpoint, -}; + +use splinter_rest_api_common::response_models::ErrorResponse; use scabbard::protocol; use scabbard::service::{Scabbard, SERVICE_TYPE}; @@ -29,6 +27,9 @@ use splinter_rest_api_common::scabbard::batch_statuses::BatchInfoResponse; #[cfg(feature = "authorization")] use splinter_rest_api_common::scabbard::SCABBARD_READ_PERMISSION; +use crate::framework::{Method, ProtocolVersionRangeGuard}; +use crate::service::ServiceEndpoint; + const DEFAULT_BATCH_STATUS_WAIT_SECS: u64 = 300; pub fn make_get_batch_status_endpoint() -> ServiceEndpoint { diff --git a/rest_api/actix_web_1/src/scabbard/batches.rs b/rest_api/actix_web_1/src/scabbard/batches.rs index afbe888579..f41f1c06a7 100644 --- a/rest_api/actix_web_1/src/scabbard/batches.rs +++ b/rest_api/actix_web_1/src/scabbard/batches.rs @@ -19,17 +19,17 @@ use transact::protos::FromBytes; use actix_web::{web, Error as ActixError, HttpResponse}; use futures::{stream::Stream, Future, IntoFuture}; -use splinter::{ - rest_api::{ErrorResponse, Method, ProtocolVersionRangeGuard}, - service::rest_api::ServiceEndpoint, -}; use scabbard::protocol; use scabbard::service::{Scabbard, SERVICE_TYPE}; +use splinter_rest_api_common::response_models::ErrorResponse; use splinter_rest_api_common::scabbard::batches::BatchLinkResponse; #[cfg(feature = "authorization")] use splinter_rest_api_common::scabbard::SCABBARD_WRITE_PERMISSION; +use crate::framework::{Method, ProtocolVersionRangeGuard}; +use crate::service::ServiceEndpoint; + pub fn make_add_batches_to_queue_endpoint() -> ServiceEndpoint { ServiceEndpoint { service_type: SERVICE_TYPE.into(), diff --git a/rest_api/actix_web_1/src/scabbard/mod.rs b/rest_api/actix_web_1/src/scabbard/mod.rs index ed08d9d395..b1813628e3 100644 --- a/rest_api/actix_web_1/src/scabbard/mod.rs +++ b/rest_api/actix_web_1/src/scabbard/mod.rs @@ -17,9 +17,10 @@ pub mod batches; pub mod state; pub mod state_address; pub mod state_root; +#[cfg(feature = "websocket")] pub mod ws_subscribe; -use splinter::service::rest_api::{ServiceEndpoint, ServiceEndpointProvider}; +use crate::service::{ServiceEndpoint, ServiceEndpointProvider}; pub struct ScabbardServiceEndpointProvider { endpoints: Vec, @@ -41,6 +42,7 @@ impl Default for ScabbardServiceEndpointProvider { fn default() -> Self { let endpoints = vec![ batches::make_add_batches_to_queue_endpoint(), + #[cfg(feature = "websocket")] ws_subscribe::make_subscribe_endpoint(), batch_statuses::make_get_batch_status_endpoint(), state_address::make_get_state_at_address_endpoint(), @@ -50,3 +52,734 @@ impl Default for ScabbardServiceEndpointProvider { Self::new(endpoints) } } + +#[cfg(feature = "CALEB")] +mod tests { + + use std::collections::HashMap; + use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }; + use std::time::{Duration, SystemTime}; + + use actix_web::web; + use actix_web::HttpResponse; + use futures::future::IntoFuture; + use scabbard::client::ReqwestScabbardClientBuilder; + use scabbard::protocol::SCABBARD_PROTOCOL_VERSION; + use scabbard::service::{BatchInfo, BatchStatus}; + use splinter::error::InternalError; + use splinter::service::ServiceId; + #[cfg(feature = "authorization")] + use splinter_rest_api_common::auth::{ + AuthorizationHandler, AuthorizationHandlerResult, Permission, + }; + use splinter_rest_api_common::auth::{AuthorizationHeader, Identity, IdentityProvider}; + use splinter_rest_api_common::error::RestApiServerError; + use splinter_rest_api_common::response_models::ErrorResponse; + + use crate::framework::{ + AuthConfig, Method, ProtocolVersionRangeGuard, Resource, RestApiBuilder, + RestApiShutdownHandle, + }; + + const SCABBARD_ADD_BATCHES_PROTOCOL_MIN: u32 = 1; + const SCABBARD_BATCH_STATUSES_PROTOCOL_MIN: u32 = 1; + const SCABBARD_GET_STATE_PROTOCOL_MIN: u32 = 1; + const SCABBARD_LIST_STATE_PROTOCOL_MIN: u32 = 1; + const SCABBARD_STATE_ROOT_PROTOCOL_MIN: u32 = 1; + + const MOCK_CIRCUIT_ID: &str = "01234-abcde"; + const MOCK_SERVICE_ID: &str = "ABCD"; + const MOCK_BATCH_ID: &str = "batch_id"; + const MOCK_STATE_ROOT_HASH: &str = "abcd"; + + const MOCK_AUTH: &str = "Bearer Cylinder:eyJhbGciOiJzZWNwMjU2azEiLCJ0eXAiOiJjeWxpbmRlcitqd3QifQ==.\ + eyJpc3MiOiIwMjA5MWEwNmNjNDZjNWUwZDg4ZTg5Mjg0OTM2ZWRiMTY4MDBiMDNiNTZhOGYxYjdlYzI5MmYyMzJiN2M4Mzg1YTIifQ==.\ + tOMakxmebss0WGWcvKCQhYo2AAo3aaMDPS28y9nfVnMXiYq98Be08CdxB0gXCY5qYHZSw53+kjuIG+8gPhXLBA=="; + + // These have to be redefined here because the `scabbard::service::rest_api` module where these + // are originally defined is private + #[cfg(feature = "authorization")] + const SCABBARD_READ_PERMISSION: Permission = Permission::Check { + permission_id: "scabbard.read", + permission_display_name: "Scabbard read", + permission_description: + "Allows the client to read scabbard services' state and batch statuses", + }; + #[cfg(feature = "authorization")] + const SCABBARD_WRITE_PERMISSION: Permission = Permission::Check { + permission_id: "scabbard.write", + permission_display_name: "Scabbard write", + permission_description: "Allows the client to submit batches to scabbard services", + }; + + /// Verify the `ScabbardClient::submit` method works properly. + #[test] + fn submit() { + let mut resource_manager = ResourceManager::new(); + let (shutdown_handle, join_handle, bind_url) = + run_rest_api_on_open_port(resource_manager.resources()); + + let client = ReqwestScabbardClientBuilder::new() + .with_url(&format!("http://{}", bind_url)) + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + + let service_id = ServiceId::new(MOCK_CIRCUIT_ID, MOCK_SERVICE_ID); + + // Verify that a basic batch submission with no wait time is successful + client + .submit(&service_id, vec![], None) + .expect("Failed to submit batches without wait"); + + // Verify that basic batch submission with a wait time is successful + client + .submit(&service_id, vec![], Some(Duration::from_secs(1))) + .expect("Failed to submit batches with wait"); + + // Verify that an invalid URL results in an error being returned + let client = ReqwestScabbardClientBuilder::new() + .with_url("not a valid URL") + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + assert!(client.submit(&service_id, vec![], None,).is_err()); + + // Verify that an error response code results in an error being returned + resource_manager.internal_server_error(true); + assert!(client.submit(&service_id, vec![], None,).is_err()); + resource_manager.internal_server_error(false); + + // Verify that an invalid batch results in an error being returned when `wait` is requested + resource_manager.invalid_batch(true); + assert!(client + .submit(&service_id, vec![], Some(Duration::from_secs(1)),) + .is_err()); + resource_manager.invalid_batch(false); + + // Verify that a batch not getting committed before the `wait` time elapses results in an + // error being returned + resource_manager.dont_commit(true); + assert!(client + .submit(&service_id, vec![], Some(Duration::from_secs(1)),) + .is_err()); + resource_manager.dont_commit(false); + + shutdown_handle + .shutdown() + .expect("unable to shutdown rest api"); + join_handle.join().expect("Unable to join rest api thread"); + } + + /// Verify that the `ScabbardClient::get_state_at_address` method works properly. + #[test] + fn get_state_at_address() { + let mut resource_manager = ResourceManager::new(); + let (shutdown_handle, join_handle, bind_url) = + run_rest_api_on_open_port(resource_manager.resources()); + + let client = ReqwestScabbardClientBuilder::new() + .with_url(&format!("http://{}", bind_url)) + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + let service_id = ServiceId::new(MOCK_CIRCUIT_ID, MOCK_SERVICE_ID); + + // Verify that a request for an existing entry is successful and returns the right value + let value = client + .get_state_at_address(&service_id, &mock_state_entry().address) + .expect("Failed to get state for existing entry"); + assert_eq!(value, Some(mock_state_entry().value)); + + // Verify that a request for a non-existent entry is successful and returns `None` + let value = client + .get_state_at_address(&service_id, "012345") + .expect("Failed to get state for non-existent entry"); + assert_eq!(value, None); + + // Verify that an invalid URL results in an error being returned + let client = ReqwestScabbardClientBuilder::new() + .with_url("not a valid URL") + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + assert!(client.submit(&service_id, vec![], None,).is_err()); + + // Verify that an invalid address results in an error being returned + assert!(client + .get_state_at_address(&service_id, "not a valid address") + .is_err()); + + // Verify that an error response code results in an error being returned + resource_manager.internal_server_error(true); + assert!(client + .get_state_at_address(&service_id, &mock_state_entry().address) + .is_err()); + resource_manager.internal_server_error(false); + + shutdown_handle + .shutdown() + .expect("unable to shutdown rest api"); + join_handle.join().expect("Unable to join rest api thread"); + } + + /// Verify that the `ScabbardClient::get_state_with_prefix` method works properly. + #[test] + fn get_state_with_prefix() { + let mut resource_manager = ResourceManager::new(); + let (shutdown_handle, join_handle, bind_url) = + run_rest_api_on_open_port(resource_manager.resources()); + + let client = ReqwestScabbardClientBuilder::new() + .with_url(&format!("http://{}", bind_url)) + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + let service_id = ServiceId::new(MOCK_CIRCUIT_ID, MOCK_SERVICE_ID); + + // Verify that a request with no prefix is successful and returns the right value + let entries = client + .get_state_with_prefix(&service_id, None) + .expect("Failed to get all entries"); + assert_eq!(entries, vec![mock_state_entry().into()]); + + // Verify that a request with a prefix that contains an existing entry is successful and + // returns the right value + let entries = client + .get_state_with_prefix(&service_id, Some(&mock_state_entry().address[..2])) + .expect("Failed to get entries under prefix with existing entry"); + assert_eq!(entries, vec![mock_state_entry().into()]); + + // Verify that a request with a prefix that does not contain any existing entries is + // successful and returns the right value + let entries = client + .get_state_with_prefix(&service_id, Some("01")) + .expect("Failed to get entries under prefix with existing entry"); + assert_eq!(entries, vec![]); + + // Verify that an invalid URL results in an error being returned + let client = ReqwestScabbardClientBuilder::new() + .with_url("not a valid URL") + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + assert!(client.submit(&service_id, vec![], None,).is_err()); + + // Verify that an invalid address prefix results in an error being returned + assert!(client + .get_state_with_prefix(&service_id, Some("not a valid address")) + .is_err()); + + // Verify that an error response code results in an error being returned + resource_manager.internal_server_error(true); + assert!(client.get_state_with_prefix(&service_id, None).is_err()); + resource_manager.internal_server_error(false); + + shutdown_handle + .shutdown() + .expect("unable to shutdown rest api"); + join_handle.join().expect("Unable to join rest api thread"); + } + + /// Verify that the `ScabbardClient::get_current_state_root` method works properly. + #[test] + fn get_current_state_root() { + let mut resource_manager = ResourceManager::new(); + let (shutdown_handle, join_handle, bind_url) = + run_rest_api_on_open_port(resource_manager.resources()); + + let client = ReqwestScabbardClientBuilder::new() + .with_url(&format!("http://{}", bind_url)) + .with_auth(MOCK_AUTH) + .build() + .expect("unable to build client"); + let service_id = ServiceId::new(MOCK_CIRCUIT_ID, MOCK_SERVICE_ID); + + // Verify that a request returns the right value + let state_root_hash = client + .get_current_state_root(&service_id) + .expect("Failed to get state root hash"); + assert_eq!(&state_root_hash, MOCK_STATE_ROOT_HASH); + + // Verify that an error response code results in an error being returned + resource_manager.internal_server_error(true); + assert!(client.get_current_state_root(&service_id).is_err()); + resource_manager.internal_server_error(false); + + shutdown_handle + .shutdown() + .expect("unable to shutdown rest api"); + join_handle.join().expect("Unable to join rest api thread"); + } + + struct ResourceManager { + resources: Vec, + internal_server_error: Arc, + invalid_batch: Arc, + dont_commit: Arc, + } + + impl ResourceManager { + fn new() -> Self { + let scabbard_base = format!("/scabbard/{}/{}", MOCK_CIRCUIT_ID, MOCK_SERVICE_ID); + + let internal_server_error = Arc::new(AtomicBool::new(false)); + let invalid_batch = Arc::new(AtomicBool::new(false)); + let dont_commit = Arc::new(AtomicBool::new(false)); + + let mut resources = vec![]; + + let scabbard_base_clone = scabbard_base.clone(); + let internal_server_error_clone = internal_server_error.clone(); + let mut batches = Resource::build(&format!("{}/batches", scabbard_base)) + .add_request_guard(ProtocolVersionRangeGuard::new( + SCABBARD_ADD_BATCHES_PROTOCOL_MIN, + SCABBARD_PROTOCOL_VERSION, + )); + #[cfg(feature = "authorization")] + { + batches = + batches.add_method(Method::Post, SCABBARD_WRITE_PERMISSION, move |_, _| { + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else { + let link = Link { + link: format!( + "{}/batch_statuses?ids={}", + scabbard_base_clone, MOCK_BATCH_ID + ), + }; + Box::new(HttpResponse::Accepted().json(link).into_future()) + } + }); + } + #[cfg(not(feature = "authorization"))] + { + batches = batches.add_method(Method::Post, move |_, _| { + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else { + let link = Link { + link: format!( + "{}/batch_statuses?ids={}", + scabbard_base_clone, MOCK_BATCH_ID + ), + }; + Box::new(HttpResponse::Accepted().json(link).into_future()) + } + }); + } + resources.push(batches); + + let internal_server_error_clone = internal_server_error.clone(); + let invalid_batch_clone = invalid_batch.clone(); + let dont_commit_clone = dont_commit.clone(); + let mut batch_statuses = Resource::build(&format!("{}/batch_statuses", scabbard_base)) + .add_request_guard(ProtocolVersionRangeGuard::new( + SCABBARD_BATCH_STATUSES_PROTOCOL_MIN, + SCABBARD_PROTOCOL_VERSION, + )); + #[cfg(feature = "authorization")] + { + batch_statuses = batch_statuses.add_method( + Method::Get, + SCABBARD_READ_PERMISSION, + move |_, _| { + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else if invalid_batch_clone.load(Ordering::SeqCst) { + Box::new( + HttpResponse::Ok() + .json(vec![BatchInfo { + id: MOCK_BATCH_ID.into(), + status: BatchStatus::Invalid(vec![]), + timestamp: SystemTime::now(), + }]) + .into_future(), + ) + } else if dont_commit_clone.load(Ordering::SeqCst) { + Box::new( + HttpResponse::Ok() + .json(vec![BatchInfo { + id: MOCK_BATCH_ID.into(), + status: BatchStatus::Pending, + timestamp: SystemTime::now(), + }]) + .into_future(), + ) + } else { + Box::new( + HttpResponse::Ok() + .json(vec![BatchInfo { + id: MOCK_BATCH_ID.into(), + status: BatchStatus::Committed(vec![]), + timestamp: SystemTime::now(), + }]) + .into_future(), + ) + } + }, + ); + } + #[cfg(not(feature = "authorization"))] + { + batch_statuses = batch_statuses.add_method(Method::Get, move |_, _| { + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else if invalid_batch_clone.load(Ordering::SeqCst) { + Box::new( + HttpResponse::Ok() + .json(vec![BatchInfo { + id: MOCK_BATCH_ID.into(), + status: BatchStatus::Invalid(vec![]), + timestamp: SystemTime::now(), + }]) + .into_future(), + ) + } else if dont_commit_clone.load(Ordering::SeqCst) { + Box::new( + HttpResponse::Ok() + .json(vec![BatchInfo { + id: MOCK_BATCH_ID.into(), + status: BatchStatus::Pending, + timestamp: SystemTime::now(), + }]) + .into_future(), + ) + } else { + Box::new( + HttpResponse::Ok() + .json(vec![BatchInfo { + id: MOCK_BATCH_ID.into(), + status: BatchStatus::Committed(vec![]), + timestamp: SystemTime::now(), + }]) + .into_future(), + ) + } + }); + } + resources.push(batch_statuses); + + let internal_server_error_clone = internal_server_error.clone(); + let mut state_address = + Resource::build(&format!("{}/state/{{address}}", scabbard_base)).add_request_guard( + ProtocolVersionRangeGuard::new( + SCABBARD_GET_STATE_PROTOCOL_MIN, + SCABBARD_PROTOCOL_VERSION, + ), + ); + #[cfg(feature = "authorization")] + { + state_address = state_address.add_method( + Method::Get, + SCABBARD_READ_PERMISSION, + move |request, _| { + let address = request + .match_info() + .get("address") + .expect("address should not be none"); + + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else if address == mock_state_entry().address { + Box::new( + HttpResponse::Ok() + .json(mock_state_entry().value) + .into_future(), + ) + } else { + let response = ErrorResponse { + message: "Not found".into(), + }; + Box::new(HttpResponse::NotFound().json(response).into_future()) + } + }, + ); + } + #[cfg(not(feature = "authorization"))] + { + state_address = state_address.add_method(Method::Get, move |request, _| { + let address = request + .match_info() + .get("address") + .expect("address should not be none"); + + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else if address == mock_state_entry().address { + Box::new( + HttpResponse::Ok() + .json(mock_state_entry().value) + .into_future(), + ) + } else { + let response = ErrorResponse { + message: "Not found".into(), + }; + Box::new(HttpResponse::NotFound().json(response).into_future()) + } + }); + } + resources.push(state_address); + + let internal_server_error_clone = internal_server_error.clone(); + let mut state = Resource::build(&format!("{}/state", scabbard_base)).add_request_guard( + ProtocolVersionRangeGuard::new( + SCABBARD_LIST_STATE_PROTOCOL_MIN, + SCABBARD_PROTOCOL_VERSION, + ), + ); + #[cfg(feature = "authorization")] + { + state = + state.add_method(Method::Get, SCABBARD_READ_PERMISSION, move |request, _| { + let query: web::Query> = + web::Query::from_query(request.query_string()) + .expect("Failed to get query string"); + let prefix = query.get("prefix").map(String::as_str); + + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else { + let return_entry = match prefix { + Some(prefix) => mock_state_entry().address.starts_with(prefix), + None => true, + }; + let entries = if return_entry { + vec![mock_state_entry()] + } else { + vec![] + }; + Box::new(HttpResponse::Ok().json(entries).into_future()) + } + }); + } + #[cfg(not(feature = "authorization"))] + { + state = state.add_method(Method::Get, move |request, _| { + let query: web::Query> = + web::Query::from_query(request.query_string()) + .expect("Failed to get query string"); + let prefix = query.get("prefix").map(String::as_str); + + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else { + let return_entry = match prefix { + Some(prefix) => mock_state_entry().address.starts_with(prefix), + None => true, + }; + let entries = if return_entry { + vec![mock_state_entry()] + } else { + vec![] + }; + Box::new(HttpResponse::Ok().json(entries).into_future()) + } + }); + } + resources.push(state); + + let internal_server_error_clone = internal_server_error.clone(); + let mut state_root = Resource::build(&format!("{}/state_root", scabbard_base)) + .add_request_guard(ProtocolVersionRangeGuard::new( + SCABBARD_STATE_ROOT_PROTOCOL_MIN, + SCABBARD_PROTOCOL_VERSION, + )); + #[cfg(feature = "authorization")] + { + state_root = + state_root.add_method(Method::Get, SCABBARD_READ_PERMISSION, move |_, _| { + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else { + Box::new(HttpResponse::Ok().json(MOCK_STATE_ROOT_HASH).into_future()) + } + }); + } + #[cfg(not(feature = "authorization"))] + { + state_root = state_root.add_method(Method::Get, move |_, _| { + if internal_server_error_clone.load(Ordering::SeqCst) { + let response = ErrorResponse { + message: "Request failed".into(), + }; + Box::new( + HttpResponse::InternalServerError() + .json(response) + .into_future(), + ) + } else { + Box::new(HttpResponse::Ok().json(MOCK_STATE_ROOT_HASH).into_future()) + } + }); + } + resources.push(state_root); + + Self { + resources, + internal_server_error, + invalid_batch, + dont_commit, + } + } + + fn resources(&self) -> Vec { + self.resources.clone() + } + + fn internal_server_error(&mut self, val: bool) { + self.internal_server_error.store(val, Ordering::SeqCst); + } + + fn invalid_batch(&mut self, val: bool) { + self.invalid_batch.store(val, Ordering::SeqCst); + } + + fn dont_commit(&mut self, val: bool) { + self.dont_commit.store(val, Ordering::SeqCst); + } + } + + fn mock_state_entry() -> JsonStateEntry { + JsonStateEntry { + address: "abcdef".into(), + value: b"value".to_vec(), + } + } + + fn run_rest_api_on_open_port( + resources: Vec, + ) -> (RestApiShutdownHandle, std::thread::JoinHandle<()>, String) { + (10000..20000) + .find_map(|port| { + let bind_url = format!("127.0.0.1:{}", port); + let rest_api_builder = RestApiBuilder::new() + .with_bind(&bind_url) + .add_resources(resources.clone()) + .with_auth_configs(vec![AuthConfig::Custom { + resources: vec![], + identity_provider: Box::new(AlwaysAcceptIdentityProvider), + }]); + #[cfg(feature = "authorization")] + let rest_api_builder = rest_api_builder + .with_authorization_handlers(vec![Box::new(AlwaysAllowAuthorizationHandler)]); + let result = rest_api_builder + .build() + .expect("Failed to build REST API") + .run(); + match result { + Ok((shutdown_handle, join_handle)) => { + Some((shutdown_handle, join_handle, bind_url)) + } + Err(RestApiServerError::BindError(_)) => None, + Err(err) => panic!("Failed to run REST API: {}", err), + } + }) + .expect("No port available") + } + + /// An identity provider that always returns `Ok(Some(_))` + #[derive(Clone)] + struct AlwaysAcceptIdentityProvider; + + impl IdentityProvider for AlwaysAcceptIdentityProvider { + fn get_identity( + &self, + _authorization: &AuthorizationHeader, + ) -> Result, InternalError> { + Ok(Some(Identity::Custom("identity".into()))) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } + + /// An authorization handler that always returns `Ok(AuthorizationHandlerResult::Allow)` + #[cfg(feature = "authorization")] + #[derive(Clone)] + struct AlwaysAllowAuthorizationHandler; + + #[cfg(feature = "authorization")] + impl AuthorizationHandler for AlwaysAllowAuthorizationHandler { + fn has_permission( + &self, + _identity: &Identity, + _permission_id: &str, + ) -> Result { + Ok(AuthorizationHandlerResult::Allow) + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + } +} diff --git a/rest_api/actix_web_1/src/scabbard/state.rs b/rest_api/actix_web_1/src/scabbard/state.rs index 9b504c2f41..97f75a10c8 100644 --- a/rest_api/actix_web_1/src/scabbard/state.rs +++ b/rest_api/actix_web_1/src/scabbard/state.rs @@ -17,17 +17,17 @@ use std::sync::Arc; use actix_web::{web, HttpResponse}; use futures::IntoFuture; -use splinter::{ - rest_api::{ErrorResponse, Method, ProtocolVersionRangeGuard}, - service::rest_api::ServiceEndpoint, -}; use scabbard::protocol; use scabbard::service::{Scabbard, SERVICE_TYPE}; +use splinter_rest_api_common::response_models::ErrorResponse; use splinter_rest_api_common::scabbard::state::StateEntryResponse; #[cfg(feature = "authorization")] use splinter_rest_api_common::scabbard::SCABBARD_READ_PERMISSION; +use crate::framework::{Method, ProtocolVersionRangeGuard}; +use crate::service::ServiceEndpoint; + pub fn make_get_state_with_prefix_endpoint() -> ServiceEndpoint { ServiceEndpoint { service_type: SERVICE_TYPE.into(), diff --git a/rest_api/actix_web_1/src/scabbard/state_address.rs b/rest_api/actix_web_1/src/scabbard/state_address.rs index 614f16d035..40daa3d8f1 100644 --- a/rest_api/actix_web_1/src/scabbard/state_address.rs +++ b/rest_api/actix_web_1/src/scabbard/state_address.rs @@ -18,13 +18,13 @@ use actix_web::HttpResponse; use futures::IntoFuture; use scabbard::protocol; use scabbard::service::{Scabbard, SERVICE_TYPE}; -use splinter::{ - rest_api::{ErrorResponse, Method, ProtocolVersionRangeGuard}, - service::rest_api::ServiceEndpoint, -}; +use splinter_rest_api_common::response_models::ErrorResponse; #[cfg(feature = "authorization")] use splinter_rest_api_common::scabbard::SCABBARD_READ_PERMISSION; +use crate::framework::{Method, ProtocolVersionRangeGuard}; +use crate::service::ServiceEndpoint; + pub fn make_get_state_at_address_endpoint() -> ServiceEndpoint { ServiceEndpoint { service_type: SERVICE_TYPE.into(), diff --git a/rest_api/actix_web_1/src/scabbard/state_root.rs b/rest_api/actix_web_1/src/scabbard/state_root.rs index 5f6e64d4ed..5fe5dfa5a7 100644 --- a/rest_api/actix_web_1/src/scabbard/state_root.rs +++ b/rest_api/actix_web_1/src/scabbard/state_root.rs @@ -16,16 +16,16 @@ use std::sync::Arc; use actix_web::HttpResponse; use futures::IntoFuture; -use splinter::{ - rest_api::{ErrorResponse, Method, ProtocolVersionRangeGuard}, - service::rest_api::ServiceEndpoint, -}; use scabbard::protocol; use scabbard::service::{Scabbard, SERVICE_TYPE}; +use splinter_rest_api_common::response_models::ErrorResponse; #[cfg(feature = "authorization")] use splinter_rest_api_common::scabbard::SCABBARD_READ_PERMISSION; +use crate::framework::{Method, ProtocolVersionRangeGuard}; +use crate::service::ServiceEndpoint; + pub fn make_get_state_root_endpoint() -> ServiceEndpoint { ServiceEndpoint { service_type: SERVICE_TYPE.into(), diff --git a/rest_api/actix_web_1/src/scabbard/ws_subscribe.rs b/rest_api/actix_web_1/src/scabbard/ws_subscribe.rs index 11d0585aef..e05af58e42 100644 --- a/rest_api/actix_web_1/src/scabbard/ws_subscribe.rs +++ b/rest_api/actix_web_1/src/scabbard/ws_subscribe.rs @@ -17,21 +17,20 @@ use std::sync::Arc; use actix_web::{web, HttpResponse}; use futures::IntoFuture; -use splinter::{ - rest_api::{ - new_websocket_event_sender, ErrorResponse, EventSender, Method, ProtocolVersionRangeGuard, - Request, - }, - service::rest_api::ServiceEndpoint, -}; use scabbard::protocol; use scabbard::service::{ Scabbard, StateChangeEvent, StateSubscriber, StateSubscriberError, SERVICE_TYPE, }; +use splinter_rest_api_common::response_models::ErrorResponse; #[cfg(feature = "authorization")] use splinter_rest_api_common::scabbard::SCABBARD_READ_PERMISSION; +use crate::framework::{ + new_websocket_event_sender, EventSender, Method, ProtocolVersionRangeGuard, Request, +}; +use crate::service::ServiceEndpoint; + struct WsStateSubscriber { sender: EventSender, } diff --git a/rest_api/actix_web_1/src/service/builder.rs b/rest_api/actix_web_1/src/service/builder.rs index eee38257d3..88f8f2a269 100644 --- a/rest_api/actix_web_1/src/service/builder.rs +++ b/rest_api/actix_web_1/src/service/builder.rs @@ -18,15 +18,15 @@ use std::sync::Mutex; use actix_web::HttpResponse; use futures::IntoFuture; use splinter::error::InternalError; -use splinter::rest_api::Resource; +use splinter::runtime::service::instance::ServiceOrchestrator; use splinter::runtime::service::instance::{ManagedService, ServiceDefinition}; use splinter::service::instance::OrchestratableService; -use splinter::{ - runtime::service::instance::ServiceOrchestrator, service::rest_api::ServiceEndpointProvider, -}; use super::ServiceOrchestratorRestResourceProvider; +use crate::framework::Resource; +use crate::service::ServiceEndpointProvider; + #[derive(Default)] pub struct ServiceOrchestratorRestResourceProviderBuilder { providers: HashMap>, diff --git a/libsplinter/src/service/rest_api.rs b/rest_api/actix_web_1/src/service/endpoints.rs similarity index 92% rename from libsplinter/src/service/rest_api.rs rename to rest_api/actix_web_1/src/service/endpoints.rs index 4e1dc83b5f..048f7af142 100644 --- a/libsplinter/src/service/rest_api.rs +++ b/rest_api/actix_web_1/src/service/endpoints.rs @@ -16,14 +16,13 @@ use std::sync::Arc; +use crate::framework::{Method, RequestGuard}; use actix_web::{web, Error as ActixError, HttpRequest, HttpResponse}; use futures::Future; - -use crate::rest_api::actix_web_1::{Method, RequestGuard}; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; +use splinter_rest_api_common::auth::Permission; -use super::instance::ServiceInstance; +use splinter::service::instance::ServiceInstance; /// The type for functions that handle REST API requests made to service endpoints. pub type Handler = Arc< @@ -82,4 +81,4 @@ pub trait ServiceEndpointProvider { } } -type ServiceRequestGuard = Arc; +pub type ServiceRequestGuard = Arc; diff --git a/rest_api/actix_web_1/src/service/mod.rs b/rest_api/actix_web_1/src/service/mod.rs index b166d74c51..ea73814463 100644 --- a/rest_api/actix_web_1/src/service/mod.rs +++ b/rest_api/actix_web_1/src/service/mod.rs @@ -13,10 +13,12 @@ // limitations under the License. mod builder; +mod endpoints; -use splinter::rest_api::actix_web_1::{Resource, RestResourceProvider}; +use crate::framework::{Resource, RestResourceProvider}; pub use builder::ServiceOrchestratorRestResourceProviderBuilder; +pub use endpoints::{ServiceEndpoint, ServiceEndpointProvider, ServiceRequestGuard}; /// The `ServiceOrchestratorRestResourceProvider` exposes REST API resources /// provided by the [`ServiceFactory::get_rest_endpoints`] methods of the diff --git a/rest_api/actix_web_1/src/status/mod.rs b/rest_api/actix_web_1/src/status/mod.rs index e4c29a66f6..10d63ffeb3 100644 --- a/rest_api/actix_web_1/src/status/mod.rs +++ b/rest_api/actix_web_1/src/status/mod.rs @@ -18,7 +18,7 @@ mod resource_provider; use actix_web::{Error, HttpResponse}; use futures::{Future, IntoFuture}; #[cfg(feature = "authorization")] -use splinter::rest_api::auth::authorization::Permission; +use splinter_rest_api_common::auth::Permission; use splinter_rest_api_common::status::Status; pub use resource_provider::StatusResourceProvider; diff --git a/rest_api/actix_web_1/src/status/resource_provider.rs b/rest_api/actix_web_1/src/status/resource_provider.rs index 3423fec38b..1dad3cb443 100644 --- a/rest_api/actix_web_1/src/status/resource_provider.rs +++ b/rest_api/actix_web_1/src/status/resource_provider.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use splinter::rest_api::{Resource, RestResourceProvider}; +use crate::framework::{Resource, RestResourceProvider}; use super::get_status; #[cfg(feature = "authorization")] @@ -44,7 +44,7 @@ impl StatusResourceProvider { #[cfg(feature = "authorization")] { let status_resource = Resource::build("/status").add_method( - splinter::rest_api::Method::Get, + crate::framework::Method::Get, STATUS_READ_PERMISSION, handle, ); @@ -54,7 +54,7 @@ impl StatusResourceProvider { #[cfg(not(feature = "authorization"))] { let status_resource = - Resource::build("/status").add_method(splinter::rest_api::Method::Get, handle); + Resource::build("/status").add_method(crate::framework::Method::Get, handle); let resources = vec![status_resource]; Self { resources } } @@ -62,7 +62,7 @@ impl StatusResourceProvider { } impl RestResourceProvider for StatusResourceProvider { - fn resources(&self) -> Vec { + fn resources(&self) -> Vec { self.resources.clone() } } diff --git a/rest_api/common/Cargo.toml b/rest_api/common/Cargo.toml index c0f07e88f0..cf4e4b6828 100644 --- a/rest_api/common/Cargo.toml +++ b/rest_api/common/Cargo.toml @@ -23,14 +23,20 @@ description = """\ """ [dependencies] +cylinder = { version = "0.2.1", optional = true } log = "0.4" rand = "0.8" jsonwebtoken = { version = "7.0" } +openssl = { version = "0.10", optional = true } +percent-encoding = { version = "2.0", optional = true } serde = { version = "1", features = ["derive"] } serde_json = { version = "1", optional = true } splinter = { path = "../../libsplinter" } scabbard = { path = "../../services/scabbard/libscabbard", optional = true } +[dev-dependencies] +tempfile = "3" + [features] default = [ "scabbard-service", @@ -41,6 +47,7 @@ stable = [ "default", # The following features are stable: "authorization", + "biome-credentials", "oauth", "rbac" ] @@ -52,7 +59,13 @@ experimental = [ ] authorization = ["splinter/authorization"] +authorization-handler-rbac = [] +biome = ["splinter/biome"] +biome-credentials = ["splinter/biome-credentials"] +cylinder-jwt = ["cylinder"] +https-bind = ["openssl"] oauth = ["splinter/oauth"] rbac = ["splinter/authorization-handler-rbac"] scabbard-service = ["scabbard", "splinter/rest-api", "splinter/rest-api-actix-web-1", "serde_json"] +registry = ["percent-encoding"] service-endpoint = [] diff --git a/libsplinter/src/rest_api/auth/authorization/allow_keys.rs b/rest_api/common/src/auth/allow_keys.rs similarity index 99% rename from libsplinter/src/rest_api/auth/authorization/allow_keys.rs rename to rest_api/common/src/auth/allow_keys.rs index 1293913048..4840e0086c 100644 --- a/libsplinter/src/rest_api/auth/authorization/allow_keys.rs +++ b/rest_api/common/src/auth/allow_keys.rs @@ -20,8 +20,10 @@ use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::SystemTime; -use crate::error::InternalError; -use crate::rest_api::auth::identity::Identity; +use log::{error, warn}; +use splinter::error::InternalError; + +use crate::auth::identity::Identity; use super::{AuthorizationHandler, AuthorizationHandlerResult}; diff --git a/libsplinter/src/rest_api/auth/authorization/mod.rs b/rest_api/common/src/auth/authorization_handler.rs similarity index 69% rename from libsplinter/src/rest_api/auth/authorization/mod.rs rename to rest_api/common/src/auth/authorization_handler.rs index 96b91efd9b..1ac189619c 100644 --- a/libsplinter/src/rest_api/auth/authorization/mod.rs +++ b/rest_api/common/src/auth/authorization_handler.rs @@ -12,29 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tools for determining client/user authorization - -#[cfg(feature = "authorization-handler-allow-keys")] -pub mod allow_keys; -mod authorization_handler_result; -#[cfg(feature = "authorization-handler-maintenance")] -pub mod maintenance; -mod permission; -mod permission_map; -#[cfg(feature = "authorization-handler-rbac")] -pub mod rbac; -pub(in crate::rest_api) mod routes; - -use crate::error::InternalError; +use splinter::error::InternalError; use super::identity::Identity; - -pub use authorization_handler_result::AuthorizationHandlerResult; -pub use permission::Permission; -pub use permission_map::PermissionMap; - -#[cfg(test)] -pub use permission_map::Method; +use super::AuthorizationHandlerResult; /// Determines if a client has some permissions pub trait AuthorizationHandler: Send + Sync { diff --git a/libsplinter/src/rest_api/auth/authorization/authorization_handler_result.rs b/rest_api/common/src/auth/authorization_handler_result.rs similarity index 100% rename from libsplinter/src/rest_api/auth/authorization/authorization_handler_result.rs rename to rest_api/common/src/auth/authorization_handler_result.rs diff --git a/libsplinter/src/rest_api/auth/authorization_header.rs b/rest_api/common/src/auth/authorization_header.rs similarity index 70% rename from libsplinter/src/rest_api/auth/authorization_header.rs rename to rest_api/common/src/auth/authorization_header.rs index a78aad3206..261dcdd1f3 100644 --- a/libsplinter/src/rest_api/auth/authorization_header.rs +++ b/rest_api/common/src/auth/authorization_header.rs @@ -14,7 +14,9 @@ use core::str::FromStr; -use crate::rest_api::auth::{BearerToken, InvalidArgumentError}; +use splinter::error::InvalidArgumentError; + +use crate::auth::BearerToken; /// The possible outcomes of attempting to authorize a client @@ -44,3 +46,26 @@ impl FromStr for AuthorizationHeader { } } } + +#[cfg(test)] +mod tests { + use super::*; + /// Verifies that the `AuthorizationHeader` enum is correctly parsed from strings + #[test] + fn parse_authorization_header() { + assert!(matches!( + "Bearer token".parse(), + Ok(AuthorizationHeader::Bearer(token)) if token == "token".parse().unwrap() + )); + + assert!(matches!( + "Unknown token".parse(), + Ok(AuthorizationHeader::Custom(auth_str)) if auth_str == "Unknown token" + )); + + assert!(matches!( + "test".parse(), + Ok(AuthorizationHeader::Custom(auth_str)) if auth_str == "test" + )); + } +} diff --git a/libsplinter/src/rest_api/auth/authorization_result.rs b/rest_api/common/src/auth/authorization_result.rs similarity index 97% rename from libsplinter/src/rest_api/auth/authorization_result.rs rename to rest_api/common/src/auth/authorization_result.rs index d1a2e4d009..034bfede47 100644 --- a/libsplinter/src/rest_api/auth/authorization_result.rs +++ b/rest_api/common/src/auth/authorization_result.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::rest_api::auth::Identity; +use crate::auth::Identity; /// The possible outcomes of attempting to authorize a client pub enum AuthorizationResult { diff --git a/libsplinter/src/rest_api/auth/bearer_token.rs b/rest_api/common/src/auth/bearer_token.rs similarity index 70% rename from libsplinter/src/rest_api/auth/bearer_token.rs rename to rest_api/common/src/auth/bearer_token.rs index 3e86abfbf3..5e3872a868 100644 --- a/libsplinter/src/rest_api/auth/bearer_token.rs +++ b/rest_api/common/src/auth/bearer_token.rs @@ -14,7 +14,7 @@ use core::str::FromStr; -use crate::rest_api::auth::InvalidArgumentError; +use splinter::error::InvalidArgumentError; /// A bearer token of a specific type #[derive(PartialEq)] @@ -59,3 +59,39 @@ impl FromStr for BearerToken { } } } + +#[cfg(test)] +mod tests { + use super::*; + /// Verifies that the `BearerToken` enum is correctly parsed from strings + #[test] + fn parse_bearer_token() { + #[cfg(feature = "biome-credentials")] + assert!(matches!( + "Biome:test".parse(), + Ok(BearerToken::Biome(token)) if token == "test" + )); + + #[cfg(feature = "cylinder-jwt")] + assert!(matches!( + "Cylinder:test".parse(), + Ok(BearerToken::Cylinder(token)) if token == "test" + )); + + #[cfg(feature = "oauth")] + assert!(matches!( + "OAuth2:test".parse(), + Ok(BearerToken::OAuth2(token)) if token == "test" + )); + + assert!(matches!( + "Unknown:test".parse(), + Ok(BearerToken::Custom(token)) if token == "Unknown:test" + )); + + assert!(matches!( + "test".parse(), + Ok(BearerToken::Custom(token)) if token == "test" + )); + } +} diff --git a/libsplinter/src/rest_api/auth/identity/biome.rs b/rest_api/common/src/auth/identity/biome.rs similarity index 97% rename from libsplinter/src/rest_api/auth/identity/biome.rs rename to rest_api/common/src/auth/identity/biome.rs index 18e0fef766..b1e03edc9d 100644 --- a/libsplinter/src/rest_api/auth/identity/biome.rs +++ b/rest_api/common/src/auth/identity/biome.rs @@ -18,12 +18,12 @@ use std::sync::Arc; use jsonwebtoken::{decode, DecodingKey, Validation}; -use crate::error::InternalError; -use crate::rest_api::{ +use crate::{ auth::{AuthorizationHeader, BearerToken}, secrets::SecretManager, sessions::Claims, }; +use splinter::error::InternalError; use super::{Identity, IdentityProvider}; diff --git a/libsplinter/src/rest_api/auth/identity/cylinder.rs b/rest_api/common/src/auth/identity/cylinder.rs similarity index 95% rename from libsplinter/src/rest_api/auth/identity/cylinder.rs rename to rest_api/common/src/auth/identity/cylinder.rs index 62e4c23eaf..3a3e9474f3 100644 --- a/libsplinter/src/rest_api/auth/identity/cylinder.rs +++ b/rest_api/common/src/auth/identity/cylinder.rs @@ -18,8 +18,8 @@ use std::sync::{Arc, Mutex}; use cylinder::{jwt::JsonWebTokenParser, Verifier}; -use crate::error::InternalError; -use crate::rest_api::auth::{AuthorizationHeader, BearerToken}; +use crate::auth::{AuthorizationHeader, BearerToken}; +use splinter::error::InternalError; use super::{Identity, IdentityProvider}; diff --git a/libsplinter/src/rest_api/auth/identity/mod.rs b/rest_api/common/src/auth/identity/mod.rs similarity index 75% rename from libsplinter/src/rest_api/auth/identity/mod.rs rename to rest_api/common/src/auth/identity/mod.rs index 7c2ddea916..fdf50b3b95 100644 --- a/libsplinter/src/rest_api/auth/identity/mod.rs +++ b/rest_api/common/src/auth/identity/mod.rs @@ -21,9 +21,9 @@ pub mod cylinder; #[cfg(feature = "oauth")] pub mod oauth; -use crate::error::InternalError; +use splinter::error::InternalError; -use super::AuthorizationHeader; +use crate::auth::AuthorizationHeader; /// A REST API client's identity as determined by an [IdentityProvider] #[derive(Debug, PartialEq)] @@ -36,6 +36,20 @@ pub enum Identity { User(String), } +#[cfg(feature = "rbac")] +impl From<&Identity> for Option { + fn from(identity: &Identity) -> Self { + match identity { + // RoleBasedAuthorization does not currently support custom identities + Identity::Custom(_) => None, + Identity::Key(key) => Some(splinter::rbac::store::Identity::Key(key.to_string())), + Identity::User(user_id) => { + Some(splinter::rbac::store::Identity::User(user_id.to_string())) + } + } + } +} + /// A service that fetches identities from a backing provider pub trait IdentityProvider: Send + Sync { /// Attempts to get the identity that corresponds to the given authorization header. This method diff --git a/rest_api/common/src/auth/identity/oauth.rs b/rest_api/common/src/auth/identity/oauth.rs new file mode 100644 index 0000000000..6d8d4bd457 --- /dev/null +++ b/rest_api/common/src/auth/identity/oauth.rs @@ -0,0 +1,181 @@ +// Copyright 2018-2022 Cargill Incorporated +// +// 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. + +//! An identity provider backed by an OAuth server + +use std::time::Duration; + +use log::debug; +use splinter::biome::OAuthUserSessionStore; +use splinter::error::InternalError; +use splinter::oauth::OAuthClient; + +use crate::auth::{AuthorizationHeader, BearerToken}; + +use super::{Identity, IdentityProvider}; + +/// The default amount of time since the last authentication for which the identity provider can +/// assume the session is still valid +const DEFAULT_REAUTHENTICATION_INTERVAL: Duration = Duration::from_secs(3600); // 1 hour + +/// An identity provider, backed by an OAuth server, that returns a user's Biome ID +/// +/// This provider uses an [OAuthUserSessionStore] as a cache of identities. The session store tracks +/// all OAuth users' sessions with a "last authenticated" timestamp. Sessions are initially added by +/// the OAuth REST API endpoints when a user logs in. +/// +/// If the session has not been authenticated within the re-authentication interval, the user will +/// be re-authenticated using the internal [OAuthClient] and the session will be updated in the +/// session store. If re-authentication fails, the session will be removed from the store and the +/// user will need to start a new session by logging in. +/// +/// This identity provider will also use a session's refresh token (if it has one) to get a new +/// OAuth access token for the session as needed. +/// +/// This provider only accepts `AuthorizationHeader::Bearer(BearerToken::OAuth2(token))` +/// authorizations, and the inner token must be a valid Splinter access token for an OAuth user. +#[derive(Clone)] +pub struct OAuthUserIdentityProvider { + oauth_client: OAuthClient, + oauth_user_session_store: Box, + reauthentication_interval: Duration, +} + +impl OAuthUserIdentityProvider { + /// Creates a new OAuth user identity provider + /// + /// # Arguments + /// + /// * `oauth_client` - The OAuth client that will be used to check if a session is still valid + /// * `oauth_user_session_store` - The store that tracks users' sessions + /// * `reauthentication_interval` - The amount of time since the last authentication for which + /// the identity provider can assume the session is still valid. If this amount of time has + /// elapsed since the last authentication of a session, the session will be re-authenticated + /// by the identity provider. If not provided, the default will be used (1 hour). + pub fn new( + oauth_client: OAuthClient, + oauth_user_session_store: Box, + reauthentication_interval: Option, + ) -> Self { + Self { + oauth_client, + oauth_user_session_store, + reauthentication_interval: reauthentication_interval + .unwrap_or(DEFAULT_REAUTHENTICATION_INTERVAL), + } + } +} + +impl IdentityProvider for OAuthUserIdentityProvider { + fn get_identity( + &self, + authorization: &AuthorizationHeader, + ) -> Result, InternalError> { + let token = match authorization { + AuthorizationHeader::Bearer(BearerToken::OAuth2(token)) => token, + _ => return Ok(None), + }; + + let session = match self + .oauth_user_session_store + .get_session(token) + .map_err(|err| InternalError::from_source(err.into()))? + { + Some(session) => session, + None => return Ok(None), + }; + + let user_id = session.user().user_id().to_string(); + + let time_since_authenticated = session + .last_authenticated() + .elapsed() + .map_err(|err| InternalError::from_source(err.into()))?; + if time_since_authenticated >= self.reauthentication_interval { + match self.oauth_client.get_subject(session.oauth_access_token()) { + Ok(Some(_)) => { + let updated_session = session.into_update_builder().build(); + self.oauth_user_session_store + .update_session(updated_session) + .map_err(|err| InternalError::from_source(err.into()))?; + Ok(Some(Identity::User(user_id))) + } + Ok(None) => { + // The access token didn't work; see if there's a refresh token that can be used + // to get a new one. + match session.oauth_refresh_token() { + Some(refresh_token) => { + // Try using the session's OAuth refresh token to get a new OAuth + // access token + match self + .oauth_client + .exchange_refresh_token(refresh_token.to_string()) + { + Ok(access_token) => { + // Update the access token in the store + let updated_session = session + .into_update_builder() + .with_oauth_access_token(access_token.clone()) + .build(); + self.oauth_user_session_store + .update_session(updated_session) + .map_err(|err| InternalError::from_source(err.into()))?; + // Authenticate with the new access token; if this fails (we + // get Ok(None) or Err(_)), something's wrong that can't be + // handled here. + match self.oauth_client.get_subject(&access_token)? { + Some(_) => Ok(Some(Identity::User(user_id))), + None => Err(InternalError::with_message( + "failed to authenticate user with new access token" + .into(), + )), + } + } + Err(err) => { + // The refresh token didn't work; delete the session since it's + // no longer valid + debug!("Failed to exchange refresh token: {}", err); + self.oauth_user_session_store + .remove_session(token) + .map_err(|err| InternalError::from_source(err.into()))?; + Ok(None) + } + } + } + None => { + // The access token didn't work and there's no refresh token for this + // session; delete the session since it's no longer valid. + self.oauth_user_session_store + .remove_session(token) + .map_err(|err| InternalError::from_source(err.into()))?; + Ok(None) + } + } + } + Err(err) => { + self.oauth_user_session_store + .remove_session(token) + .map_err(|err| InternalError::from_source(err.into()))?; + Err(err) + } + } + } else { + Ok(Some(Identity::User(user_id))) + } + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} diff --git a/libsplinter/src/rest_api/auth/authorization/maintenance/mod.rs b/rest_api/common/src/auth/maintenance/authorization_handler.rs similarity index 94% rename from libsplinter/src/rest_api/auth/authorization/maintenance/mod.rs rename to rest_api/common/src/auth/maintenance/authorization_handler.rs index 8bd1a0d690..14f09e8667 100644 --- a/libsplinter/src/rest_api/auth/authorization/maintenance/mod.rs +++ b/rest_api/common/src/auth/maintenance/authorization_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2018-2020 Cargill Incorporated +// Copyright 2018-2022 Cargill Incorporated // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,21 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! An authorization handler that allows write permissions to be temporarily revoked +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; -mod routes; - -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, -}; - -use crate::error::InternalError; +use splinter::error::InternalError; #[cfg(feature = "authorization-handler-rbac")] -use crate::rbac::store::{Identity as RBACIdentity, RoleBasedAuthorizationStore, ADMIN_ROLE_ID}; -use crate::rest_api::auth::identity::Identity; +use splinter::rbac::store::{Identity as RBACIdentity, RoleBasedAuthorizationStore, ADMIN_ROLE_ID}; -use super::{AuthorizationHandler, AuthorizationHandlerResult}; +use crate::auth::{identity::Identity, AuthorizationHandler, AuthorizationHandlerResult}; /// An authorization handler that allows write permissions to be temporarily revoked /// @@ -119,12 +112,15 @@ impl AuthorizationHandler for MaintenanceModeAuthorizationHandler { mod tests { use super::*; - use crate::rbac::store::{ - Assignment, AssignmentBuilder, Role, RoleBasedAuthorizationStore, - RoleBasedAuthorizationStoreError, + #[cfg(feature = "authorization-handler-rbac")] + use splinter::rbac::store::{ + Assignment, AssignmentBuilder, Identity as RBACIdentity, Role, RoleBasedAuthorizationStore, + RoleBasedAuthorizationStoreError, ADMIN_ROLE_ID, }; + #[cfg(feature = "authorization-handler-rbac")] const ADMIN_USER_IDENTITY: &str = "admin_user"; + #[cfg(feature = "authorization-handler-rbac")] const NON_ADMIN_USER_IDENTITY: &str = "non_admin_user"; /// Verifies that the maintenance mode authorization handler returns a `Continue` for all read @@ -231,6 +227,7 @@ mod tests { #[derive(Clone)] struct MockRoleBasedAuthorizationStore; + #[cfg(feature = "authorization-handler-rbac")] impl RoleBasedAuthorizationStore for MockRoleBasedAuthorizationStore { fn get_role(&self, _id: &str) -> Result, RoleBasedAuthorizationStoreError> { unimplemented!() diff --git a/libsplinter/src/oauth/rest_api/actix/mod.rs b/rest_api/common/src/auth/maintenance/mod.rs similarity index 79% rename from libsplinter/src/oauth/rest_api/actix/mod.rs rename to rest_api/common/src/auth/maintenance/mod.rs index cb26255e23..ebd78f70ee 100644 --- a/libsplinter/src/oauth/rest_api/actix/mod.rs +++ b/rest_api/common/src/auth/maintenance/mod.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub(super) mod callback; -pub(super) mod list_users; -pub(super) mod login; -pub(super) mod logout; +mod authorization_handler; +mod resources; + +pub use authorization_handler::MaintenanceModeAuthorizationHandler; +pub use resources::PostMaintenanceModeQuery; diff --git a/libsplinter/src/rest_api/auth/authorization/maintenance/routes/resources.rs b/rest_api/common/src/auth/maintenance/resources.rs similarity index 96% rename from libsplinter/src/rest_api/auth/authorization/maintenance/routes/resources.rs rename to rest_api/common/src/auth/maintenance/resources.rs index b410d8a54f..7151f4685f 100644 --- a/libsplinter/src/rest_api/auth/authorization/maintenance/routes/resources.rs +++ b/rest_api/common/src/auth/maintenance/resources.rs @@ -15,6 +15,8 @@ //! This module provides resources for the maintenance mode authorization handler's REST API //! endpoints +use serde::Deserialize; + #[derive(Deserialize)] pub struct PostMaintenanceModeQuery { pub enabled: bool, diff --git a/libsplinter/src/rest_api/auth/mod.rs b/rest_api/common/src/auth/mod.rs similarity index 92% rename from libsplinter/src/rest_api/auth/mod.rs rename to rest_api/common/src/auth/mod.rs index 590a88f8c7..2d5fece3c0 100644 --- a/libsplinter/src/rest_api/auth/mod.rs +++ b/rest_api/common/src/auth/mod.rs @@ -12,34 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Authentication and authorization for Splinter - -#[cfg(feature = "rest-api-actix-web-1")] -pub(crate) mod actix; -#[cfg(feature = "authorization")] -pub mod authorization; -#[cfg(feature = "rest-api-actix-web-1")] +mod allow_keys; +mod authorization_handler; +mod authorization_handler_result; mod authorization_header; -#[cfg(feature = "rest-api-actix-web-1")] mod authorization_result; mod bearer_token; -pub mod identity; - -#[cfg(feature = "rest-api-actix-web-1")] +mod identity; +mod maintenance; +#[cfg(feature = "oauth")] +mod oauth; +mod permission; +mod permission_map; +#[cfg(feature = "rbac")] +pub mod rbac; +mod resources; + +use log::error; + +pub use authorization_handler::AuthorizationHandler; +pub use authorization_handler_result::AuthorizationHandlerResult; pub use authorization_header::AuthorizationHeader; -#[cfg(feature = "rest-api-actix-web-1")] pub use authorization_result::AuthorizationResult; pub use bearer_token::BearerToken; - -use crate::error::InvalidArgumentError; - -#[cfg(feature = "authorization")] -use super::Method; - -#[cfg(feature = "authorization")] -use authorization::{AuthorizationHandler, AuthorizationHandlerResult, Permission, PermissionMap}; -#[cfg(feature = "rest-api-actix-web-1")] -use identity::{Identity, IdentityProvider}; +pub use identity::IdentityProvider; +pub use maintenance::{MaintenanceModeAuthorizationHandler, PostMaintenanceModeQuery}; + +pub use allow_keys::AllowKeysAuthorizationHandler; +#[cfg(feature = "biome-credentials")] +pub use identity::biome::BiomeUserIdentityProvider; +#[cfg(feature = "cylinder-jwt")] +pub use identity::cylinder::CylinderKeyIdentityProvider; +#[cfg(feature = "oauth")] +pub use identity::oauth::OAuthUserIdentityProvider; +pub use identity::Identity; +#[cfg(feature = "oauth")] +pub use oauth::resources::{ + generate_redirect_query, CallbackQuery, ListOAuthUserResponse, OAuthUserResponse, PagingQuery, +}; +#[cfg(all(feature = "authorization", feature = "oauth"))] +pub use oauth::OAUTH_USER_READ_PERMISSION; +pub use permission::Permission; +pub use permission_map::PermissionMap; +#[cfg(feature = "rbac")] +pub use rbac::RoleBasedAuthorizationHandler; +pub use resources::PermissionResponse; + +pub const RBAC_READ_PERMISSION: Permission = Permission::Check { + permission_id: "authorization.rbac.read", + permission_display_name: "RBAC read", + permission_description: "Allows the client to read roles, identities, and role assignments", +}; + +pub const RBAC_WRITE_PERMISSION: Permission = Permission::Check { + permission_id: "authorization.rbac.write", + permission_display_name: "RBAC write", + permission_description: "Allows the client to modify roles, identities, and role assignments", +}; /// Uses the given identity providers to check authorization for the request. This function is /// backend-agnostic and intended as a helper for the backend REST API implementations. @@ -52,9 +81,8 @@ use identity::{Identity, IdentityProvider}; /// * `identity_providers` - The identity providers that will be used to check the client's identity /// * `authorization_handlers` - The authorization handlers that will be used to check the client's /// permissions -#[cfg(feature = "rest-api-actix-web-1")] -fn authorize( - #[cfg(feature = "authorization")] method: &Method, +pub fn authorize( + #[cfg(feature = "authorization")] method: &M, #[cfg(any( feature = "authorization", feature = "biome-credentials", @@ -68,7 +96,7 @@ fn authorize( )))] _endpoint: &str, auth_header: Option<&str>, - #[cfg(feature = "authorization")] permission_map: &PermissionMap, + #[cfg(feature = "authorization")] permission_map: &PermissionMap, identity_providers: &[Box], #[cfg(feature = "authorization")] authorization_handlers: &[Box], ) -> AuthorizationResult { @@ -138,7 +166,6 @@ fn authorize( } } -#[cfg(feature = "rest-api-actix-web-1")] fn get_identity( auth_header: Option<&str>, identity_providers: &[Box], @@ -156,58 +183,8 @@ fn get_identity( mod tests { use super::*; - use crate::error::InternalError; - - /// Verifies that the `AuthorizationHeader` enum is correctly parsed from strings - #[test] - fn parse_authorization_header() { - assert!(matches!( - "Bearer token".parse(), - Ok(AuthorizationHeader::Bearer(token)) if token == "token".parse().unwrap() - )); - - assert!(matches!( - "Unknown token".parse(), - Ok(AuthorizationHeader::Custom(auth_str)) if auth_str == "Unknown token" - )); - - assert!(matches!( - "test".parse(), - Ok(AuthorizationHeader::Custom(auth_str)) if auth_str == "test" - )); - } - - /// Verifies that the `BearerToken` enum is correctly parsed from strings - #[test] - fn parse_bearer_token() { - #[cfg(feature = "biome-credentials")] - assert!(matches!( - "Biome:test".parse(), - Ok(BearerToken::Biome(token)) if token == "test" - )); - - #[cfg(feature = "cylinder-jwt")] - assert!(matches!( - "Cylinder:test".parse(), - Ok(BearerToken::Cylinder(token)) if token == "test" - )); - - #[cfg(feature = "oauth")] - assert!(matches!( - "OAuth2:test".parse(), - Ok(BearerToken::OAuth2(token)) if token == "test" - )); - - assert!(matches!( - "Unknown:test".parse(), - Ok(BearerToken::Custom(token)) if token == "Unknown:test" - )); - - assert!(matches!( - "test".parse(), - Ok(BearerToken::Custom(token)) if token == "test" - )); - } + use crate::auth::permission_map::Method; + use splinter::error::InternalError; /// Verifies that the `authorize` function returns `AuthorizationResult::Unauthorized` when no /// identity providers are specified. Without any identity providers, there's no way to properly @@ -226,7 +203,7 @@ mod tests { }; assert!(matches!( - authorize( + authorize::( #[cfg(feature = "authorization")] &Method::Get, "/test/endpoint", @@ -257,7 +234,7 @@ mod tests { }; assert!(matches!( - authorize( + authorize::( #[cfg(feature = "authorization")] &Method::Get, "/test/endpoint", @@ -289,7 +266,7 @@ mod tests { }; assert!(matches!( - authorize( + authorize::( #[cfg(feature = "authorization")] &Method::Get, "/test/endpoint", @@ -489,7 +466,7 @@ mod tests { }; assert!(matches!( - authorize( + authorize::( #[cfg(feature = "authorization")] &Method::Get, "/test/endpoint", @@ -528,7 +505,7 @@ mod tests { }; assert!(matches!( - authorize( + authorize::( #[cfg(feature = "authorization")] &Method::Get, "/test/endpoint", @@ -571,7 +548,7 @@ mod tests { }; assert!(matches!( - authorize( + authorize::( #[cfg(feature = "authorization")] &Method::Get, "/test/endpoint", diff --git a/libsplinter/src/oauth/rest_api/mod.rs b/rest_api/common/src/auth/oauth/mod.rs similarity index 76% rename from libsplinter/src/oauth/rest_api/mod.rs rename to rest_api/common/src/auth/oauth/mod.rs index 1d7d3d805a..0de0da5fa9 100644 --- a/libsplinter/src/oauth/rest_api/mod.rs +++ b/rest_api/common/src/auth/oauth/mod.rs @@ -12,19 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! OAuth REST API endpoints - -mod actix; -mod resource_provider; -mod resources; +pub mod resources; #[cfg(feature = "authorization")] -use crate::rest_api::auth::authorization::Permission; - -pub use resource_provider::OAuthResourceProvider; +use crate::auth::Permission; #[cfg(feature = "authorization")] -const OAUTH_USER_READ_PERMISSION: Permission = Permission::Check { +pub const OAUTH_USER_READ_PERMISSION: Permission = Permission::Check { permission_id: "oauth.users.read", permission_display_name: "OAuth Users read", permission_description: "Allows the client to read OAuth users", diff --git a/libsplinter/src/oauth/rest_api/resources/callback.rs b/rest_api/common/src/auth/oauth/resources/callback.rs similarity index 97% rename from libsplinter/src/oauth/rest_api/resources/callback.rs rename to rest_api/common/src/auth/oauth/resources/callback.rs index ab0a4d5fea..bcb681089b 100644 --- a/libsplinter/src/oauth/rest_api/resources/callback.rs +++ b/rest_api/common/src/auth/oauth/resources/callback.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use serde::Deserialize; + #[derive(Deserialize)] pub struct CallbackQuery { pub code: String, diff --git a/libsplinter/src/oauth/rest_api/resources/list_users.rs b/rest_api/common/src/auth/oauth/resources/list_users.rs similarity index 85% rename from libsplinter/src/oauth/rest_api/resources/list_users.rs rename to rest_api/common/src/auth/oauth/resources/list_users.rs index 5fe9a95d75..c56a30af09 100644 --- a/libsplinter/src/oauth/rest_api/resources/list_users.rs +++ b/rest_api/common/src/auth/oauth/resources/list_users.rs @@ -14,17 +14,18 @@ //! Defines OAuthUsers returned by the `OAuthResourceProvider`. -use crate::biome::oauth::store::OAuthUser; -use crate::rest_api::paging::{Paging, DEFAULT_LIMIT, DEFAULT_OFFSET}; +use crate::paging::v1::{Paging, DEFAULT_LIMIT, DEFAULT_OFFSET}; +use serde::{self, Deserialize, Serialize}; +use splinter::biome::oauth::store::OAuthUser; #[derive(Serialize)] -pub(crate) struct ListOAuthUserResponse<'a> { +pub struct ListOAuthUserResponse<'a> { pub data: Vec>, pub paging: Paging, } #[derive(Serialize)] -pub(crate) struct OAuthUserResponse<'a> { +pub struct OAuthUserResponse<'a> { pub subject: &'a str, pub user_id: &'a str, } diff --git a/libsplinter/src/oauth/rest_api/resources/mod.rs b/rest_api/common/src/auth/oauth/resources/mod.rs similarity index 82% rename from libsplinter/src/oauth/rest_api/resources/mod.rs rename to rest_api/common/src/auth/oauth/resources/mod.rs index ab3b824f6d..28d371b15c 100644 --- a/libsplinter/src/oauth/rest_api/resources/mod.rs +++ b/rest_api/common/src/auth/oauth/resources/mod.rs @@ -14,3 +14,6 @@ pub(super) mod callback; pub(super) mod list_users; + +pub use callback::{generate_redirect_query, CallbackQuery}; +pub use list_users::{ListOAuthUserResponse, OAuthUserResponse, PagingQuery}; diff --git a/libsplinter/src/rest_api/auth/authorization/permission.rs b/rest_api/common/src/auth/permission.rs similarity index 100% rename from libsplinter/src/rest_api/auth/authorization/permission.rs rename to rest_api/common/src/auth/permission.rs diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/mod.rs b/rest_api/common/src/auth/permission_map/method.rs similarity index 82% rename from libsplinter/src/rest_api/auth/authorization/rbac/mod.rs rename to rest_api/common/src/auth/permission_map/method.rs index 090df2f089..c400b16ef0 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/mod.rs +++ b/rest_api/common/src/auth/permission_map/method.rs @@ -12,7 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod handler; -pub mod rest_api; - -pub use handler::RoleBasedAuthorizationHandler; +#[allow(dead_code)] +#[derive(PartialEq, Clone)] +pub enum Method { + Get, + Post, + Put, + Patch, + Delete, + Head, +} diff --git a/libsplinter/src/rest_api/auth/authorization/permission_map/mod.rs b/rest_api/common/src/auth/permission_map/mod.rs similarity index 83% rename from libsplinter/src/rest_api/auth/authorization/permission_map/mod.rs rename to rest_api/common/src/auth/permission_map/mod.rs index e7652d080c..89d3f3df6e 100644 --- a/libsplinter/src/rest_api/auth/authorization/permission_map/mod.rs +++ b/rest_api/common/src/auth/permission_map/mod.rs @@ -81,14 +81,12 @@ impl PermissionMap { mod tests { use super::*; - use crate::rest_api::actix_web_1::Method as Actix1Method; - /// Verifies that a `PathComponent` is correctly parsed #[test] fn path_component_parse() { - assert!(PathComponent::from("") == PathComponent::Text("".into())); - assert!(PathComponent::from("test") == PathComponent::Text("test".into())); - assert!(PathComponent::from("{test}") == PathComponent::Variable); + assert!(PathComponent::Text("".into()) == ""); + assert!(PathComponent::Text("test".into()) == "test"); + assert!(PathComponent::Variable == "{test}"); } /// Verifies that a `PathComponent` can be correctly compared with a `&str` @@ -136,36 +134,33 @@ mod tests { let mut map = PermissionMap::new(); assert!(map.internal.is_empty()); - map.add_permission(Actix1Method::Get, "/test/endpoint", perm1); + map.add_permission(Method::Get, "/test/endpoint", perm1); assert_eq!(map.internal.len(), 1); assert_eq!( - map.get_permission(&Actix1Method::Get, "/test/endpoint"), + map.get_permission(&Method::Get, "/test/endpoint"), Some(&perm1) ); - assert_eq!( - map.get_permission(&Actix1Method::Put, "/test/endpoint"), - None - ); - assert_eq!(map.get_permission(&Actix1Method::Get, "/test/other"), None); + assert_eq!(map.get_permission(&Method::Put, "/test/endpoint"), None); + assert_eq!(map.get_permission(&Method::Get, "/test/other"), None); let mut other_map = PermissionMap::new(); - other_map.add_permission(Actix1Method::Put, "/test/endpoint/{variable}", perm2); + other_map.add_permission(Method::Put, "/test/endpoint/{variable}", perm2); map.append(&mut other_map); assert_eq!(map.internal.len(), 2); assert_eq!( - map.get_permission(&Actix1Method::Get, "/test/endpoint"), + map.get_permission(&Method::Get, "/test/endpoint"), Some(&perm1) ); assert_eq!( - map.get_permission(&Actix1Method::Put, "/test/endpoint/test1"), + map.get_permission(&Method::Put, "/test/endpoint/test1"), Some(&perm2) ); assert_eq!( - map.get_permission(&Actix1Method::Put, "/test/endpoint/test2"), + map.get_permission(&Method::Put, "/test/endpoint/test2"), Some(&perm2) ); assert_eq!( - map.get_permission(&Actix1Method::Get, "/test/endpoint/test1"), + map.get_permission(&Method::Get, "/test/endpoint/test1"), None ); } diff --git a/libsplinter/src/rest_api/auth/authorization/permission_map/path_component.rs b/rest_api/common/src/auth/permission_map/path_component.rs similarity index 100% rename from libsplinter/src/rest_api/auth/authorization/permission_map/path_component.rs rename to rest_api/common/src/auth/permission_map/path_component.rs diff --git a/libsplinter/src/rest_api/auth/authorization/permission_map/request_definition.rs b/rest_api/common/src/auth/permission_map/request_definition.rs similarity index 100% rename from libsplinter/src/rest_api/auth/authorization/permission_map/request_definition.rs rename to rest_api/common/src/auth/permission_map/request_definition.rs diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/error.rs b/rest_api/common/src/auth/rbac/error.rs similarity index 94% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/error.rs rename to rest_api/common/src/auth/rbac/error.rs index e86976e3d1..32957bf998 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/actix_web_1/error.rs +++ b/rest_api/common/src/auth/rbac/error.rs @@ -17,11 +17,11 @@ use std::error::Error; use std::fmt; -use crate::error::{ConstraintViolationType, InvalidStateError}; -use crate::rbac::store::RoleBasedAuthorizationStoreError; +use splinter::error::{ConstraintViolationType, InvalidStateError}; +use splinter::rbac::store::RoleBasedAuthorizationStoreError; #[derive(Debug)] -pub(crate) enum SendableRoleBasedAuthorizationStoreError { +pub enum SendableRoleBasedAuthorizationStoreError { ConstraintViolation(String), InternalError(String), InvalidState(InvalidStateError), diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/handler.rs b/rest_api/common/src/auth/rbac/handler.rs similarity index 97% rename from libsplinter/src/rest_api/auth/authorization/rbac/handler.rs rename to rest_api/common/src/auth/rbac/handler.rs index 540de73fbe..ea508c366b 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/handler.rs +++ b/rest_api/common/src/auth/rbac/handler.rs @@ -12,14 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::error::InternalError; +use splinter::error::InternalError; +use splinter::rbac::store::{RoleBasedAuthorizationStore, ADMIN_ROLE_ID}; -use crate::rest_api::auth::{ - authorization::{AuthorizationHandler, AuthorizationHandlerResult}, - identity::Identity, -}; - -use crate::rbac::store::{RoleBasedAuthorizationStore, ADMIN_ROLE_ID}; +use crate::auth::{AuthorizationHandler, AuthorizationHandlerResult, Identity}; /// A Role-based authorization handler. /// diff --git a/rest_api/common/src/auth/rbac/mod.rs b/rest_api/common/src/auth/rbac/mod.rs new file mode 100644 index 0000000000..d75c6ef0fb --- /dev/null +++ b/rest_api/common/src/auth/rbac/mod.rs @@ -0,0 +1,26 @@ +// Copyright 2018-2022 Cargill Incorporated +// +// 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. + +mod error; +mod handler; +mod resources; + +pub use error::SendableRoleBasedAuthorizationStoreError; +pub use handler::RoleBasedAuthorizationHandler; +pub use resources::assignments::{ + AssignmentPayload, AssignmentResponse, AssignmentUpdatePayload, IdentityPayload, + IdentityResponse, ListAssignmentsResponse, +}; +pub use resources::roles::{ListRoleResponse, RolePayload, RoleResponse, RoleUpdatePayload}; +pub use resources::PagingQuery; diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/assignments.rs b/rest_api/common/src/auth/rbac/resources/assignments.rs similarity index 93% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/assignments.rs rename to rest_api/common/src/auth/rbac/resources/assignments.rs index f1c000ebb1..8d50930d70 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/assignments.rs +++ b/rest_api/common/src/auth/rbac/resources/assignments.rs @@ -14,9 +14,11 @@ use std::convert::TryFrom; -use crate::error::InvalidStateError; -use crate::rbac::store::{Assignment, AssignmentBuilder, Identity}; -use crate::rest_api::paging::Paging; +use serde::{Deserialize, Serialize}; +use splinter::error::InvalidStateError; +use splinter::rbac::store::{Assignment, AssignmentBuilder, Identity}; + +use crate::paging::v1::Paging; #[derive(Serialize)] pub struct ListAssignmentsResponse<'a> { diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/mod.rs b/rest_api/common/src/auth/rbac/resources/mod.rs similarity index 92% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/mod.rs rename to rest_api/common/src/auth/rbac/resources/mod.rs index 86b7a68641..4b0bf2adcd 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/mod.rs +++ b/rest_api/common/src/auth/rbac/resources/mod.rs @@ -14,10 +14,12 @@ //! Web-framework-agnostic resources. +use serde::Deserialize; + pub mod assignments; pub mod roles; -use crate::rest_api::paging::{DEFAULT_LIMIT, DEFAULT_OFFSET}; +use crate::paging::v1::{DEFAULT_LIMIT, DEFAULT_OFFSET}; #[derive(Deserialize)] pub struct PagingQuery { diff --git a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/roles.rs b/rest_api/common/src/auth/rbac/resources/roles.rs similarity index 92% rename from libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/roles.rs rename to rest_api/common/src/auth/rbac/resources/roles.rs index 54754aba2e..f6fa31ebb4 100644 --- a/libsplinter/src/rest_api/auth/authorization/rbac/rest_api/resources/roles.rs +++ b/rest_api/common/src/auth/rbac/resources/roles.rs @@ -16,9 +16,11 @@ use std::convert::TryFrom; -use crate::error::InvalidStateError; -use crate::rbac::store::{Role, RoleBuilder}; -use crate::rest_api::paging::Paging; +use serde::{Deserialize, Serialize}; +use splinter::error::InvalidStateError; +use splinter::rbac::store::{Role, RoleBuilder}; + +use crate::paging::v1::Paging; #[derive(Serialize)] pub struct ListRoleResponse<'a> { diff --git a/libsplinter/src/rest_api/auth/authorization/routes/resources.rs b/rest_api/common/src/auth/resources.rs similarity index 97% rename from libsplinter/src/rest_api/auth/authorization/routes/resources.rs rename to rest_api/common/src/auth/resources.rs index 09f9cc9083..ff09aff4cc 100644 --- a/libsplinter/src/rest_api/auth/authorization/routes/resources.rs +++ b/rest_api/common/src/auth/resources.rs @@ -14,6 +14,8 @@ //! Resources for the authorization tools' REST API endpoints +use serde::Serialize; + #[derive(Serialize)] pub struct PermissionResponse { pub permission_id: &'static str, diff --git a/libsplinter/src/rest_api/bind_config.rs b/rest_api/common/src/bind_config.rs similarity index 100% rename from libsplinter/src/rest_api/bind_config.rs rename to rest_api/common/src/bind_config.rs diff --git a/libsplinter/src/rest_api/errors/mod.rs b/rest_api/common/src/error/mod.rs similarity index 92% rename from libsplinter/src/rest_api/errors/mod.rs rename to rest_api/common/src/error/mod.rs index b04a4dd5ca..2e3899a748 100644 --- a/libsplinter/src/rest_api/errors/mod.rs +++ b/rest_api/common/src/error/mod.rs @@ -13,7 +13,9 @@ // limitations under the License. mod request_error; +mod response_error; mod rest_api_server_error; pub use request_error::RequestError; +pub use response_error::ResponseError; pub use rest_api_server_error::RestApiServerError; diff --git a/libsplinter/src/rest_api/errors/request_error.rs b/rest_api/common/src/error/request_error.rs similarity index 100% rename from libsplinter/src/rest_api/errors/request_error.rs rename to rest_api/common/src/error/request_error.rs diff --git a/rest_api/common/src/error.rs b/rest_api/common/src/error/response_error.rs similarity index 100% rename from rest_api/common/src/error.rs rename to rest_api/common/src/error/response_error.rs diff --git a/libsplinter/src/rest_api/errors/rest_api_server_error.rs b/rest_api/common/src/error/rest_api_server_error.rs similarity index 96% rename from libsplinter/src/rest_api/errors/rest_api_server_error.rs rename to rest_api/common/src/error/rest_api_server_error.rs index 364bb516dd..71e7521789 100644 --- a/libsplinter/src/rest_api/errors/rest_api_server_error.rs +++ b/rest_api/common/src/error/rest_api_server_error.rs @@ -15,9 +15,9 @@ use std::error::Error; use std::fmt; -use crate::error::{InternalError, InvalidStateError}; +use splinter::error::{InternalError, InvalidStateError}; #[cfg(feature = "oauth")] -use crate::oauth::OAuthClientBuildError; +use splinter::oauth::OAuthClientBuildError; /// Error module for `rest_api`. #[derive(Debug)] diff --git a/rest_api/common/src/lib.rs b/rest_api/common/src/lib.rs index 5cf1f2460e..0955dc8be3 100644 --- a/rest_api/common/src/lib.rs +++ b/rest_api/common/src/lib.rs @@ -12,10 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +pub mod auth; +pub mod bind_config; pub mod error; +#[cfg(feature = "oauth")] +pub mod oauth_config; pub mod paging; +#[cfg(feature = "registry")] +pub mod percent_encode_filter_query; +pub mod response_models; #[cfg(feature = "scabbard")] pub mod scabbard; +pub mod secrets; +pub mod sessions; pub mod status; pub const SPLINTER_PROTOCOL_VERSION: u32 = 2; diff --git a/libsplinter/src/rest_api/oauth_config.rs b/rest_api/common/src/oauth_config.rs similarity index 98% rename from libsplinter/src/rest_api/oauth_config.rs rename to rest_api/common/src/oauth_config.rs index 20f399f8ca..0384c935ac 100644 --- a/libsplinter/src/rest_api/oauth_config.rs +++ b/rest_api/common/src/oauth_config.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::oauth::store::InflightOAuthRequestStore; +use splinter::oauth::store::InflightOAuthRequestStore; /// OAuth configurations that are supported out-of-the-box by the Splinter REST API. pub enum OAuthConfig { diff --git a/rest_api/common/src/paging/v1/mod.rs b/rest_api/common/src/paging/v1/mod.rs index f30e2eecce..43128f725a 100644 --- a/rest_api/common/src/paging/v1/mod.rs +++ b/rest_api/common/src/paging/v1/mod.rs @@ -37,6 +37,10 @@ impl Paging { pub fn builder(link: String, query_count: usize) -> PagingBuilder { PagingBuilder::new(link, query_count) } + + pub fn get_next(&self) -> &str { + &self.next + } } #[cfg(test)] diff --git a/rest_api/common/src/percent_encode_filter_query.rs b/rest_api/common/src/percent_encode_filter_query.rs new file mode 100644 index 0000000000..76151742aa --- /dev/null +++ b/rest_api/common/src/percent_encode_filter_query.rs @@ -0,0 +1,34 @@ +// Copyright 2018-2022 Cargill Incorporated +// +// 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. + +use percent_encoding::{AsciiSet, CONTROLS}; + +const QUERY_ENCODE_SET: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'<') + .add(b'>') + .add(b'`') + .add(b'=') + .add(b'!') + .add(b'{') + .add(b'}') + .add(b'[') + .add(b']') + .add(b':') + .add(b','); + +pub fn percent_encode_filter_query(input: &str) -> String { + percent_encoding::utf8_percent_encode(input, QUERY_ENCODE_SET).to_string() +} diff --git a/libsplinter/src/rest_api/response_models.rs b/rest_api/common/src/response_models.rs similarity index 98% rename from libsplinter/src/rest_api/response_models.rs rename to rest_api/common/src/response_models.rs index 4094328723..e61002c720 100644 --- a/libsplinter/src/rest_api/response_models.rs +++ b/rest_api/common/src/response_models.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use serde::{Deserialize, Serialize}; + /// Model for a error response to an REST request #[derive(Debug, Serialize, Deserialize)] pub struct ErrorResponse { diff --git a/rest_api/common/src/scabbard/mod.rs b/rest_api/common/src/scabbard/mod.rs index 6966871a57..86df89a8af 100644 --- a/rest_api/common/src/scabbard/mod.rs +++ b/rest_api/common/src/scabbard/mod.rs @@ -17,7 +17,7 @@ pub mod batches; pub mod state; #[cfg(feature = "authorization")] -use splinter::rest_api::auth::authorization::Permission; +use crate::auth::Permission; #[cfg(feature = "authorization")] pub const SCABBARD_READ_PERMISSION: Permission = Permission::Check { diff --git a/libsplinter/src/rest_api/secrets/auto_secret_manager.rs b/rest_api/common/src/secrets/auto_secret_manager.rs similarity index 100% rename from libsplinter/src/rest_api/secrets/auto_secret_manager.rs rename to rest_api/common/src/secrets/auto_secret_manager.rs diff --git a/libsplinter/src/rest_api/secrets/error.rs b/rest_api/common/src/secrets/error.rs similarity index 100% rename from libsplinter/src/rest_api/secrets/error.rs rename to rest_api/common/src/secrets/error.rs diff --git a/libsplinter/src/rest_api/secrets/mod.rs b/rest_api/common/src/secrets/mod.rs similarity index 100% rename from libsplinter/src/rest_api/secrets/mod.rs rename to rest_api/common/src/secrets/mod.rs diff --git a/libsplinter/src/rest_api/sessions/claims.rs b/rest_api/common/src/sessions/claims.rs similarity index 99% rename from libsplinter/src/rest_api/sessions/claims.rs rename to rest_api/common/src/sessions/claims.rs index 3b00c441d6..135bb265c1 100644 --- a/libsplinter/src/rest_api/sessions/claims.rs +++ b/rest_api/common/src/sessions/claims.rs @@ -17,6 +17,8 @@ use std::collections::HashMap; use std::time::{Duration, SystemTime, SystemTimeError, UNIX_EPOCH}; +use serde::{Deserialize, Serialize}; + use super::ClaimsBuildError; /// Defines payload of a JWT Token diff --git a/libsplinter/src/rest_api/sessions/error.rs b/rest_api/common/src/sessions/error.rs similarity index 99% rename from libsplinter/src/rest_api/sessions/error.rs rename to rest_api/common/src/sessions/error.rs index 4943459a7a..aa9c150268 100644 --- a/libsplinter/src/rest_api/sessions/error.rs +++ b/rest_api/common/src/sessions/error.rs @@ -17,7 +17,7 @@ use std::fmt; use jsonwebtoken::errors::{Error as JWTError, ErrorKind}; -use crate::rest_api::secrets::SecretManagerError; +use crate::secrets::SecretManagerError; /// Error for TokenIssuer #[derive(Debug)] diff --git a/libsplinter/src/rest_api/sessions/mod.rs b/rest_api/common/src/sessions/mod.rs similarity index 93% rename from libsplinter/src/rest_api/sessions/mod.rs rename to rest_api/common/src/sessions/mod.rs index 8c9f09efd9..ab8f31dc35 100644 --- a/libsplinter/src/rest_api/sessions/mod.rs +++ b/rest_api/common/src/sessions/mod.rs @@ -39,7 +39,7 @@ pub trait TokenIssuer { } #[cfg(feature = "biome-credentials")] -pub(crate) fn default_validation(issuer: &str) -> Validation { +pub fn default_validation(issuer: &str) -> Validation { Validation { leeway: DEFAULT_LEEWAY, iss: Some(issuer.to_string()), @@ -49,7 +49,7 @@ pub(crate) fn default_validation(issuer: &str) -> Validation { /// Validates authorization token but ignores the expiration date #[cfg(feature = "biome-credentials")] -pub(crate) fn ignore_exp_validation(issuer: &str) -> Validation { +pub fn ignore_exp_validation(issuer: &str) -> Validation { Validation { leeway: DEFAULT_LEEWAY, iss: Some(issuer.to_string()), diff --git a/libsplinter/src/rest_api/sessions/token_issuer.rs b/rest_api/common/src/sessions/token_issuer.rs similarity index 97% rename from libsplinter/src/rest_api/sessions/token_issuer.rs rename to rest_api/common/src/sessions/token_issuer.rs index 683e3906db..a840d12ef4 100644 --- a/libsplinter/src/rest_api/sessions/token_issuer.rs +++ b/rest_api/common/src/sessions/token_issuer.rs @@ -19,7 +19,7 @@ use std::sync::Arc; use jsonwebtoken::{encode, EncodingKey, Header}; use super::{Claims, TokenIssuer, TokenIssuerError}; -use crate::rest_api::secrets::SecretManager; +use crate::secrets::SecretManager; /// Issues JWT access tokens pub struct AccessTokenIssuer { diff --git a/services/scabbard/libscabbard/Cargo.toml b/services/scabbard/libscabbard/Cargo.toml index cea7dd8b27..969fcfe712 100644 --- a/services/scabbard/libscabbard/Cargo.toml +++ b/services/scabbard/libscabbard/Cargo.toml @@ -55,6 +55,8 @@ features = ["lmdb", "transaction-receipt-store"] [dev-dependencies] transact = { version = "0.5", features = ["family-command", "family-command-transaction-builder", "state-merkle-sql"] } splinter = { path = "../../../libsplinter", features = ["diesel"]} +splinter-rest-api-common = { path = "../../../rest_api/common" } +splinter-rest-api-actix-web-1 = { path = "../../../rest_api/actix_web_1" , features = [ "service", "scabbard-service", "authorization"] } [build-dependencies] protoc-rust = "2.14" diff --git a/services/scabbard/libscabbard/src/client/reqwest/mod.rs b/services/scabbard/libscabbard/src/client/reqwest/mod.rs index 7f90e182b7..ad6cc698ea 100644 --- a/services/scabbard/libscabbard/src/client/reqwest/mod.rs +++ b/services/scabbard/libscabbard/src/client/reqwest/mod.rs @@ -444,20 +444,17 @@ mod tests { use actix_web::web; use actix_web::HttpResponse; use futures::future::IntoFuture; + use splinter::error::InternalError; #[cfg(feature = "authorization")] - use splinter::rest_api::auth::authorization::{ - AuthorizationHandler, AuthorizationHandlerResult, Permission, + use splinter_rest_api_common::auth::{ + AuthorizationHandler, AuthorizationHandlerResult, AuthorizationHeader, Identity, + IdentityProvider, Permission, }; - use splinter::{ - error::InternalError, - rest_api::{ - auth::{ - identity::{Identity, IdentityProvider}, - AuthorizationHeader, - }, - AuthConfig, Method, ProtocolVersionRangeGuard, Resource, RestApiBuilder, - RestApiServerError, RestApiShutdownHandle, - }, + use splinter_rest_api_common::error::RestApiServerError; + + use splinter_rest_api_actix_web_1::framework::{ + AuthConfig, Method, ProtocolVersionRangeGuard, Resource, RestApiBuilder, + RestApiShutdownHandle, }; const SCABBARD_ADD_BATCHES_PROTOCOL_MIN: u32 = 1; diff --git a/splinterd/Cargo.toml b/splinterd/Cargo.toml index 7ae964ee0c..df5687e50d 100644 --- a/splinterd/Cargo.toml +++ b/splinterd/Cargo.toml @@ -48,7 +48,7 @@ sawtooth = { version = "0.7", default-features = false, optional = true } serde = "1.0.80" serde_derive = "1.0.80" splinter-echo = { path = "../services/echo/libecho", optional = true } -splinter-rest-api-actix-web-1 = { path = "../rest_api/actix_web_1" , features = ["admin-service", "registry", "service", "scabbard-service"] } +splinter-rest-api-common = { path = "../rest_api/common" } toml = "0.5" [dev-dependencies] @@ -58,6 +58,19 @@ sabre-sdk = "0.9" tempfile = "3" transact = "0.5" +[dependencies.splinter-rest-api-actix-web-1] +path = "../rest_api/actix_web_1" +features = ["admin-service", + "registry", + "service", + "scabbard-service", + "rbac", + "biome-credentials", + "cylinder-jwt", + "authorization", + "websocket" + ] + [dependencies.scabbard] path = "../services/scabbard/libscabbard" features = [ @@ -121,6 +134,7 @@ authorization = [ "scabbard/authorization", "splinter/authorization", "splinter-rest-api-actix-web-1/authorization", + "splinter-rest-api-common/authorization", ] authorization-handler-allow-keys = ["splinter/authorization-handler-allow-keys"] authorization-handler-maintenance = [ @@ -128,15 +142,17 @@ authorization-handler-maintenance = [ ] authorization-handler-rbac = [ "splinter/authorization-handler-rbac", + "splinter/authorization", + "splinter-rest-api-actix-web-1/authorization-handler-rbac", ] -biome-credentials = ["splinter/biome-credentials"] +biome-credentials = ["splinter/biome-credentials", "splinter-rest-api-common/biome-credentials"] biome-key-management = ["splinter/biome-key-management", "splinter-rest-api-actix-web-1/biome-key-management"] -biome-profile = ["splinter/biome-profile"] +biome-profile = ["splinter/biome-profile", "splinter-rest-api-actix-web-1/biome-profile"] config-allow-keys = ["authorization-handler-allow-keys"] database-postgres = ["diesel", "diesel/postgres", "scabbard/postgres", "splinter/postgres", "splinter-echo/postgres"] database-sqlite = ["diesel", "diesel/sqlite", "scabbard/sqlite", "splinter/sqlite", "splinter-echo/sqlite"] disable-scabbard-autocleanup = [] -https-bind = ["splinter/https-bind"] +https-bind = ["splinter/https-bind","splinter-rest-api-actix-web-1/https-bind","splinter-rest-api-common/https-bind"] lifecycle-executor-interval = [] tap = [ "splinter/tap", @@ -161,9 +177,11 @@ node = [ "splinter/biome-client-reqwest", ] oauth = [ - "splinter/oauth" + "splinter/oauth", + "splinter-rest-api-common/oauth", + "splinter-rest-api-actix-web-1/oauth" ] -rest-api-cors = ["splinter/rest-api-cors"] +rest-api-cors = ["splinter/rest-api-cors", "splinter-rest-api-actix-web-1/rest-api-cors"] scabbardv3 = ["scabbard/scabbardv3", "service2", "scabbard/scabbardv3-consensus",] service-endpoint = ["splinter-rest-api-actix-web-1/service-endpoint"] service-timer-interval = [] diff --git a/splinterd/src/daemon/error.rs b/splinterd/src/daemon/error.rs index 4ca60cdc7b..f5a01ee551 100644 --- a/splinterd/src/daemon/error.rs +++ b/splinterd/src/daemon/error.rs @@ -16,8 +16,8 @@ use std::error::Error; use std::fmt; use splinter::error::InternalError; -use splinter::rest_api::RestApiServerError; use splinter::transport::{AcceptError, ConnectError, ListenError}; +use splinter_rest_api_common::error::RestApiServerError; use crate::error::UserError; diff --git a/splinterd/src/daemon/mod.rs b/splinterd/src/daemon/mod.rs index 6e7ff5bf5d..3c49a03730 100644 --- a/splinterd/src/daemon/mod.rs +++ b/splinterd/src/daemon/mod.rs @@ -40,10 +40,6 @@ use scabbard::service::ScabbardFactoryBuilder; use splinter::admin::lifecycle::sync::SyncLifecycleInterface; use splinter::admin::lifecycle::LifecycleDispatch; use splinter::admin::service::{admin_service_id, AdminService, AdminServiceBuilder}; -#[cfg(feature = "biome-credentials")] -use splinter::biome::credentials::rest_api::BiomeCredentialsRestResourceProviderBuilder; -#[cfg(feature = "biome-profile")] -use splinter::biome::profile::rest_api::BiomeProfileRestResourceProvider; use splinter::circuit::handlers::{ AdminDirectMessageHandler, CircuitDirectMessageHandler, CircuitErrorHandler, CircuitMessageHandler, ServiceConnectRequestHandler, ServiceDisconnectRequestHandler, @@ -71,23 +67,6 @@ use splinter::public_key::PublicKey; use splinter::registry::{ LocalYamlRegistry, RegistryReader, RemoteYamlRegistry, RwRegistry, UnifiedRegistry, }; -#[cfg(feature = "authorization-handler-allow-keys")] -use splinter::rest_api::auth::authorization::allow_keys::AllowKeysAuthorizationHandler; -#[cfg(feature = "authorization-handler-maintenance")] -use splinter::rest_api::auth::authorization::maintenance::MaintenanceModeAuthorizationHandler; -#[cfg(feature = "authorization-handler-rbac")] -use splinter::rest_api::auth::authorization::rbac::{ - rest_api::RoleBasedAuthorizationResourceProvider, RoleBasedAuthorizationHandler, -}; -#[cfg(any( - feature = "authorization-handler-rbac", - feature = "authorization-handler-maintenance", - feature = "authorization-handler-allow-keys" -))] -use splinter::rest_api::auth::authorization::AuthorizationHandler; -#[cfg(feature = "oauth")] -use splinter::rest_api::OAuthConfig; -use splinter::rest_api::{AuthConfig, RestApiBuilder, RestResourceProvider}; use splinter::runtime::service::instance::{ ServiceOrchestratorBuilder, ServiceProcessor, ServiceProcessorShutdownHandle, }; @@ -107,13 +86,35 @@ use splinter::transport::{ #[cfg(feature = "service-echo")] use splinter_echo::service::{EchoMessageByteConverter, EchoMessageHandlerFactory}; use splinter_rest_api_actix_web_1::admin::{AdminServiceRestProvider, CircuitResourceProvider}; +#[cfg(feature = "authorization-handler-rbac")] +use splinter_rest_api_actix_web_1::auth::RoleBasedAuthorizationResourceProvider; +#[cfg(feature = "biome-credentials")] +use splinter_rest_api_actix_web_1::biome::credentials::BiomeCredentialsRestResourceProviderBuilder; #[cfg(feature = "biome-key-management")] use splinter_rest_api_actix_web_1::biome::key_management::BiomeKeyManagementRestResourceProvider; +#[cfg(feature = "biome-profile")] +use splinter_rest_api_actix_web_1::biome::profile::BiomeProfileRestResourceProvider; +use splinter_rest_api_actix_web_1::framework::AuthConfig; +use splinter_rest_api_actix_web_1::framework::{RestApiBuilder, RestResourceProvider}; use splinter_rest_api_actix_web_1::open_api; use splinter_rest_api_actix_web_1::registry::RwRegistryRestResourceProvider; use splinter_rest_api_actix_web_1::scabbard::ScabbardServiceEndpointProvider; use splinter_rest_api_actix_web_1::service::ServiceOrchestratorRestResourceProviderBuilder; use splinter_rest_api_actix_web_1::status; +#[cfg(feature = "authorization-handler-allow-keys")] +use splinter_rest_api_common::auth::AllowKeysAuthorizationHandler; +#[cfg(any( + feature = "authorization-handler-rbac", + feature = "authorization-handler-maintenance", + feature = "authorization-handler-allow-keys" +))] +use splinter_rest_api_common::auth::AuthorizationHandler; +#[cfg(feature = "authorization-handler-maintenance")] +use splinter_rest_api_common::auth::MaintenanceModeAuthorizationHandler; +#[cfg(feature = "authorization-handler-rbac")] +use splinter_rest_api_common::auth::RoleBasedAuthorizationHandler; +#[cfg(feature = "oauth")] +use splinter_rest_api_common::oauth_config::OAuthConfig; use crate::node_id::get_node_id; @@ -983,16 +984,18 @@ impl SplinterDaemon { } #[cfg(feature = "https-bind")] - fn build_rest_api_bind(&self) -> Result { + fn build_rest_api_bind( + &self, + ) -> Result { match self.rest_api_endpoint.strip_prefix("http://") { - Some(insecure_endpoint) => Ok(splinter::rest_api::BindConfig::Http( + Some(insecure_endpoint) => Ok(splinter_rest_api_common::bind_config::BindConfig::Http( insecure_endpoint.into(), )), None => { if let Some((rest_api_server_cert, rest_api_server_key)) = self.rest_api_ssl_settings.as_ref() { - Ok(splinter::rest_api::BindConfig::Https { + Ok(splinter_rest_api_common::bind_config::BindConfig::Https { bind: self .rest_api_endpoint .strip_prefix("https://") diff --git a/splinterd/src/node/builder/mod.rs b/splinterd/src/node/builder/mod.rs index cd82da31fe..8ce9a8c224 100644 --- a/splinterd/src/node/builder/mod.rs +++ b/splinterd/src/node/builder/mod.rs @@ -24,20 +24,18 @@ use std::time::Duration; use cylinder::{secp256k1::Secp256k1Context, Context, Signer, Verifier, VerifierFactory}; use rand::{thread_rng, Rng}; -use splinter::biome::credentials::rest_api::{ - BiomeCredentialsRestResourceProvider, BiomeCredentialsRestResourceProviderBuilder, -}; + use splinter::error::InternalError; use splinter::public_key::PublicKey; use splinter::rbac::store::{AssignmentBuilder, Identity as AssignmentIdentity, RoleBuilder}; -use splinter::rest_api::actix_web_1::RestApiBuilder as RestApiBuilder1; -use splinter::rest_api::auth::authorization::rbac::RoleBasedAuthorizationHandler; -use splinter::rest_api::auth::{ - authorization::{AuthorizationHandler, AuthorizationHandlerResult}, - identity::Identity, -}; -use splinter::rest_api::BindConfig; use splinter::store::{memory::MemoryStoreFactory, StoreFactory}; +use splinter_rest_api_actix_web_1::biome::credentials::{ + BiomeCredentialsRestResourceProvider, BiomeCredentialsRestResourceProviderBuilder, +}; +use splinter_rest_api_actix_web_1::framework::RestApiBuilder as RestApiBuilder1; +use splinter_rest_api_common::auth::RoleBasedAuthorizationHandler; +use splinter_rest_api_common::auth::{AuthorizationHandler, AuthorizationHandlerResult, Identity}; +use splinter_rest_api_common::bind_config::BindConfig; use super::{RunnableNode, RunnableNodeRestApiVariant, ScabbardConfig}; diff --git a/splinterd/src/node/runnable/admin.rs b/splinterd/src/node/runnable/admin.rs index 58d8af91e8..296647cf59 100644 --- a/splinterd/src/node/runnable/admin.rs +++ b/splinterd/src/node/runnable/admin.rs @@ -25,11 +25,11 @@ use splinter::events::Reactor; use splinter::peer::PeerManagerConnector; use splinter::public_key::PublicKey; use splinter::registry::{LocalYamlRegistry, RegistryReader, UnifiedRegistry}; -use splinter::rest_api::actix_web_1::RestResourceProvider as _; use splinter::runtime::service::instance::{ServiceOrchestratorBuilder, ServiceProcessorBuilder}; use splinter::store::StoreFactory; use splinter::transport::{inproc::InprocTransport, Transport}; use splinter_rest_api_actix_web_1::admin::{AdminServiceRestProvider, CircuitResourceProvider}; +use splinter_rest_api_actix_web_1::framework::RestResourceProvider as _; use splinter_rest_api_actix_web_1::registry::RwRegistryRestResourceProvider; use splinter_rest_api_actix_web_1::service::ServiceOrchestratorRestResourceProviderBuilder; diff --git a/splinterd/src/node/runnable/biome.rs b/splinterd/src/node/runnable/biome.rs index 871ac1183b..74b7e6a00e 100644 --- a/splinterd/src/node/runnable/biome.rs +++ b/splinterd/src/node/runnable/biome.rs @@ -16,16 +16,17 @@ use std::sync::Arc; -use splinter::biome::{ - credentials::rest_api::BiomeCredentialsRestResourceProviderBuilder, - profile::rest_api::BiomeProfileRestResourceProvider, UserProfileStore, -}; +use splinter::biome::UserProfileStore; use splinter::error::InternalError; -use splinter::rest_api::{ - actix_web_1::Resource as Actix1Resource, AuthConfig, RestResourceProvider, -}; use splinter::store::StoreFactory; use splinter_rest_api_actix_web_1::biome::key_management::BiomeKeyManagementRestResourceProvider; +use splinter_rest_api_actix_web_1::biome::{ + credentials::BiomeCredentialsRestResourceProviderBuilder, + profile::BiomeProfileRestResourceProvider, +}; +use splinter_rest_api_actix_web_1::{ + framework::AuthConfig, framework::Resource as Actix1Resource, framework::RestResourceProvider, +}; use crate::node::running::biome::BiomeSubsystem; diff --git a/splinterd/src/node/runnable/mod.rs b/splinterd/src/node/runnable/mod.rs index a785aa8c8a..36786efeaf 100644 --- a/splinterd/src/node/runnable/mod.rs +++ b/splinterd/src/node/runnable/mod.rs @@ -18,19 +18,15 @@ pub(super) mod admin; pub(super) mod biome; pub(super) mod network; -use splinter::biome::credentials::rest_api::BiomeCredentialsRestResourceProvider; use splinter::error::InternalError; -use splinter::rest_api::actix_web_1::RestApiBuilder; -use splinter::rest_api::{ - auth::{ - authorization::{ - maintenance::MaintenanceModeAuthorizationHandler, - rbac::rest_api::RoleBasedAuthorizationResourceProvider, - }, - identity::{Identity, IdentityProvider}, - AuthorizationHeader, - }, - AuthConfig, RestResourceProvider, +use splinter_rest_api_actix_web_1::biome::credentials::BiomeCredentialsRestResourceProvider; +use splinter_rest_api_actix_web_1::framework::RestApiBuilder; +use splinter_rest_api_actix_web_1::{ + auth::RoleBasedAuthorizationResourceProvider, framework::AuthConfig, + framework::RestResourceProvider, +}; +use splinter_rest_api_common::auth::{ + AuthorizationHeader, Identity, IdentityProvider, MaintenanceModeAuthorizationHandler, }; use super::builder::admin::AdminSubsystemBuilder; diff --git a/splinterd/src/node/running/admin.rs b/splinterd/src/node/running/admin.rs index b37bb783ac..12d4fe85d4 100644 --- a/splinterd/src/node/running/admin.rs +++ b/splinterd/src/node/running/admin.rs @@ -19,10 +19,10 @@ use splinter::error::InternalError; use splinter::events::Reactor; use splinter::peer::PeerManagerConnector; use splinter::registry::RegistryWriter; -use splinter::rest_api::actix_web_1::Resource as Actix1Resource; use splinter::runtime::service::instance::ServiceProcessorShutdownHandle; use splinter::store::StoreFactory; use splinter::threading::lifecycle::ShutdownHandle; +use splinter_rest_api_actix_web_1::framework::Resource as Actix1Resource; /// The configured, running Admin Service Event client variants. pub enum AdminServiceEventClientVariant { diff --git a/splinterd/src/node/running/mod.rs b/splinterd/src/node/running/mod.rs index 79f473b309..e43594104e 100644 --- a/splinterd/src/node/running/mod.rs +++ b/splinterd/src/node/running/mod.rs @@ -34,8 +34,8 @@ use splinter::registry::{ client::{RegistryClient, ReqwestRegistryClient}, RegistryWriter, }; -use splinter::rest_api::actix_web_1::RestApiShutdownHandle; use splinter::threading::lifecycle::ShutdownHandle; +use splinter_rest_api_actix_web_1::framework::RestApiShutdownHandle; use std::time::Duration; use super::{running::admin::AdminSubsystem, NodeBuilder, RestApiVariant, RunnableNode};