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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions libsplinter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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"]
Expand All @@ -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"]
Expand All @@ -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",
]
Expand Down
1 change: 1 addition & 0 deletions libsplinter/src/admin/service/messages/v1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 0 additions & 2 deletions libsplinter/src/biome/credentials/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
37 changes: 0 additions & 37 deletions libsplinter/src/biome/credentials/rest_api/mod.rs

This file was deleted.

2 changes: 0 additions & 2 deletions libsplinter/src/biome/profile/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,4 @@

//! Biome functionality to support user profiles.

#[cfg(feature = "rest-api-actix-web-1")]
pub mod rest_api;
pub mod store;
7 changes: 0 additions & 7 deletions libsplinter/src/biome/profile/rest_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 3 additions & 7 deletions libsplinter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions libsplinter/src/oauth/builder/openid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use reqwest::blocking::Client;
use serde::Deserialize;

use crate::error::{InternalError, InvalidStateError};
use crate::oauth::OpenIdProfileProvider;
Expand Down
17 changes: 14 additions & 3 deletions libsplinter/src/oauth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -76,7 +74,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<String>,
Expand Down Expand Up @@ -244,6 +242,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
Expand Down
207 changes: 0 additions & 207 deletions libsplinter/src/oauth/profile/openid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,210 +122,3 @@ impl From<OpenIdProfileResponse> 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");
}
}
}
Loading