-
Notifications
You must be signed in to change notification settings - Fork 59
[SCIM 3/4]: SCIM client token CRUD + Bearer auth #9180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dea9bf9
56d5288
6e67ae9
9bbb011
ed37a89
88c03f6
2b03a76
880fb63
76b452e
5cebf49
3b3b891
456cdc0
3b52803
8995f83
723848d
84ae83b
493af88
0ed8f13
5d4026b
01520c8
bb98724
3a90a98
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| // This Source Code Form is subject to the terms of the Mozilla Public | ||
| // License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| // file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
|
||
| //! SCIM-only bearer tokens | ||
|
|
||
| use super::super::Details; | ||
| use super::HttpAuthnScheme; | ||
| use super::Reason; | ||
| use super::SchemeResult; | ||
| use crate::authn; | ||
| use async_trait::async_trait; | ||
| use headers::HeaderMapExt; | ||
| use headers::authorization::{Authorization, Bearer}; | ||
|
|
||
| // This scheme is intended only for SCIM provisioning clients. | ||
| // | ||
| // For ease of integration into existing clients, we use RFC 6750 bearer tokens. | ||
| // This mechanism in turn uses HTTP's "Authorization" header. In practice, it | ||
| // looks like this: | ||
| // | ||
| // Authorization: Bearer oxide-scim-01c90c58085fed6a230d137b9b9b5e7501d0a523 | ||
| // ^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
| // | | | | | ||
| // | | | +--- specifies the token itself | ||
| // | | +--------------- specifies this "token" mechanism | ||
| // | +---------------------- specifies RFC 6750 bearer tokens | ||
| // +------------------------------------- standard HTTP authentication hdr | ||
| // | ||
| // (That's not a typo -- the "authorization" header is generally used to specify | ||
| // _authentication_ information. Similarly, the "Unauthorized" HTTP response | ||
| // code usually describes an _authentication_ error.) | ||
|
|
||
| pub const SCIM_TOKEN_SCHEME_NAME: authn::SchemeName = | ||
| authn::SchemeName("scim_token"); | ||
|
|
||
| /// Prefix used on the bearer token to identify this scheme | ||
| // RFC 6750 expects bearer tokens to be opaque base64-encoded data. In our case, | ||
| // the data we want to represent (this prefix, plus valid tokens) are subsets of | ||
| // the base64 character set, so we do not bother encoding them. | ||
| const TOKEN_PREFIX: &str = "oxide-scim-"; | ||
|
|
||
| /// Implements a SCIM provisioning client specific bearer-token-based | ||
| /// authentication scheme. | ||
| #[derive(Debug)] | ||
| pub struct HttpAuthnScimToken; | ||
|
|
||
| #[async_trait] | ||
| impl<T> HttpAuthnScheme<T> for HttpAuthnScimToken | ||
| where | ||
| T: ScimTokenContext + Send + Sync + 'static, | ||
| { | ||
| fn name(&self) -> authn::SchemeName { | ||
| SCIM_TOKEN_SCHEME_NAME | ||
| } | ||
|
|
||
| async fn authn( | ||
| &self, | ||
| ctx: &T, | ||
| _log: &slog::Logger, | ||
| request: &dropshot::RequestInfo, | ||
| ) -> SchemeResult { | ||
| let headers = request.headers(); | ||
| match parse_token(headers.typed_get().as_ref()) { | ||
| Err(error) => SchemeResult::Failed(error), | ||
| Ok(None) => SchemeResult::NotRequested, | ||
| Ok(Some(token)) => match ctx.scim_token_actor(token).await { | ||
| Err(error) => SchemeResult::Failed(error), | ||
| Ok(actor) => SchemeResult::Authenticated(Details { actor }), | ||
| }, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn parse_token( | ||
| raw_value: Option<&Authorization<Bearer>>, | ||
| ) -> Result<Option<String>, Reason> { | ||
| let token = match raw_value { | ||
| None => return Ok(None), | ||
| Some(bearer) => bearer.token(), | ||
| }; | ||
|
|
||
| if !token.starts_with(TOKEN_PREFIX) { | ||
| // This is some other kind of bearer token. Maybe another scheme knows | ||
| // how to deal with it. | ||
| return Ok(None); | ||
| } | ||
|
|
||
| Ok(Some(token[TOKEN_PREFIX.len()..].to_string())) | ||
| } | ||
|
|
||
| /// A context that can look up a Actor::Scim from a token. | ||
| #[async_trait] | ||
| pub trait ScimTokenContext { | ||
| async fn scim_token_actor( | ||
| &self, | ||
| token: String, | ||
| ) -> Result<authn::Actor, Reason>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,15 +122,26 @@ impl oso::PolarClass for AuthenticatedActor { | |
| }, | ||
| "USER_INTERNAL_API", | ||
| ) | ||
| // This is meant to guard against the SCIM actor being able to see | ||
| // the full resource hierarchy due to implicit grants in the Polar | ||
| // file. There are "if actor.is_user" guards to prevent this. | ||
| .add_attribute_getter("is_user", |a: &AuthenticatedActor| { | ||
| match a.actor { | ||
| authn::Actor::SiloUser { .. } => true, | ||
|
|
||
| authn::Actor::UserBuiltin { .. } => true, | ||
|
|
||
| authn::Actor::Scim { .. } => false, | ||
| } | ||
| }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This makes me feel bad, but it might just be the name |
||
| .add_attribute_getter("silo", |a: &AuthenticatedActor| { | ||
| match a.actor { | ||
| authn::Actor::SiloUser { silo_id, .. } => { | ||
| Some(super::Silo::new( | ||
| super::FLEET, | ||
| silo_id, | ||
| LookupType::ById(silo_id), | ||
| )) | ||
| } | ||
| authn::Actor::SiloUser { silo_id, .. } | ||
| | authn::Actor::Scim { silo_id } => Some(super::Silo::new( | ||
| super::FLEET, | ||
| silo_id, | ||
| LookupType::ById(silo_id), | ||
| )), | ||
|
|
||
| authn::Actor::UserBuiltin { .. } => None, | ||
| } | ||
|
|
@@ -149,6 +160,8 @@ impl oso::PolarClass for AuthenticatedActor { | |
| } | ||
|
|
||
| authn::Actor::UserBuiltin { .. } => false, | ||
|
|
||
| authn::Actor::Scim { .. } => false, | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to say that by "non-user actor" you mean the SCIM arm of the match. It's obvious when you're looking at the diff but might not be so obvious later.