Skip to content

Commit 67b6718

Browse files
committed
feat: migrate identity from osauth
1 parent 61b5f30 commit 67b6718

9 files changed

Lines changed: 1679 additions & 3 deletions

File tree

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ edition = "2021"
1414
rust-version = "1.82"
1515

1616
[features]
17-
default = ["baremetal", "block-storage", "compute", "image", "network", "native-tls", "object-storage"]
17+
default = ["baremetal", "block-storage", "compute", "identity", "image", "network", "native-tls", "object-storage"]
1818
baremetal = ["json-patch"]
1919
block-storage = []
2020
compute = []
21-
identity = [] # reserved for future use
21+
identity = ["static_assertions", "tokio"]
2222
image = []
2323
network = ["macaddr", "ipnet"]
2424
native-tls = ["reqwest/default-tls", "osauth/native-tls"]
@@ -40,6 +40,8 @@ serde = "^1.0"
4040
serde_derive = "^1.0"
4141
serde_json = "^1.0"
4242
serde_yaml = "^0.9"
43+
static_assertions = { version = "^1.1", optional = true }
44+
tokio = { version = "^1.0", features = ["sync"], optional = true }
4345
tokio-util = { version = "^0.7", features = ["codec", "compat"], optional = true }
4446
waiter = { version = "^0.2" }
4547

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2023 Matt Williams <matt@milliams.com>
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//! Application Credential authentication.
16+
17+
use async_trait::async_trait;
18+
use osauth::{AuthType, EndpointFilters, Error};
19+
use reqwest::{Client, RequestBuilder, Url};
20+
use static_assertions::assert_impl_all;
21+
22+
use super::internal::Internal;
23+
use super::protocol;
24+
use super::IdOrName;
25+
26+
/// Application Credential authentication using Identity API V3.
27+
///
28+
/// For any Identity authentication you need to know `auth_url`, which is an authentication endpoint
29+
/// of the Identity service. For the Application Credential authentication you also need:
30+
/// 1. Application Credential ID
31+
/// 2. Application Credential secret
32+
///
33+
/// Start with creating a `ApplicationCredential` object using [new](#method.new):
34+
///
35+
/// ```rust,no_run
36+
/// use osauth::common::IdOrName;
37+
/// let auth = osauth::identity::ApplicationCredential::new(
38+
/// "https://cloud.local/identity",
39+
/// "<a cred id>",
40+
/// "<a cred secret>",
41+
/// )
42+
/// .expect("Invalid auth_url");
43+
///
44+
/// let session = osauth::Session::new(auth);
45+
/// ```
46+
///
47+
/// The authentication token is cached while it's still valid or until
48+
/// [refresh](../trait.AuthType.html#tymethod.refresh) is called.
49+
/// Clones of an `ApplicationCredential` also start with an empty cache.
50+
#[derive(Debug, Clone)]
51+
pub struct ApplicationCredential {
52+
inner: Internal,
53+
}
54+
55+
assert_impl_all!(ApplicationCredential: Send, Sync);
56+
57+
impl ApplicationCredential {
58+
/// Create an application credential authentication.
59+
pub fn new<U, S1, S2>(auth_url: U, id: S1, secret: S2) -> Result<Self, Error>
60+
where
61+
U: AsRef<str>,
62+
S1: Into<String>,
63+
S2: Into<String>,
64+
{
65+
let app_cred = protocol::ApplicationCredential {
66+
id: IdOrName::Id(id.into()),
67+
secret: secret.into(),
68+
user: None,
69+
};
70+
let body = protocol::AuthRoot {
71+
auth: protocol::Auth {
72+
identity: protocol::Identity::ApplicationCredential(app_cred),
73+
scope: None,
74+
},
75+
};
76+
Ok(Self {
77+
inner: Internal::new(auth_url.as_ref(), body)?,
78+
})
79+
}
80+
81+
/// Create an application credential authentication from a credential name.
82+
pub fn with_user_id<U, S1, S2, S3>(
83+
auth_url: U,
84+
name: S1,
85+
secret: S2,
86+
user_id: S3,
87+
) -> Result<Self, Error>
88+
where
89+
U: AsRef<str>,
90+
S1: Into<String>,
91+
S2: Into<String>,
92+
S3: Into<String>,
93+
{
94+
let app_cred = protocol::ApplicationCredential {
95+
id: IdOrName::Name(name.into()),
96+
secret: secret.into(),
97+
user: Some(IdOrName::Id(user_id.into())),
98+
};
99+
let body = protocol::AuthRoot {
100+
auth: protocol::Auth {
101+
identity: protocol::Identity::ApplicationCredential(app_cred),
102+
scope: None,
103+
},
104+
};
105+
Ok(Self {
106+
inner: Internal::new(auth_url.as_ref(), body)?,
107+
})
108+
}
109+
110+
/// Project name or ID (if project scoped).
111+
#[inline]
112+
pub fn project(&self) -> Option<&IdOrName> {
113+
self.inner.project()
114+
}
115+
}
116+
117+
#[async_trait]
118+
impl AuthType for ApplicationCredential {
119+
/// Authenticate a request.
120+
async fn authenticate(
121+
&self,
122+
client: &Client,
123+
request: RequestBuilder,
124+
) -> Result<RequestBuilder, Error> {
125+
self.inner.authenticate(client, request).await
126+
}
127+
128+
/// Get a URL for the requested service.
129+
async fn get_endpoint(
130+
&self,
131+
client: &Client,
132+
service_type: &str,
133+
filters: &EndpointFilters,
134+
) -> Result<Url, Error> {
135+
self.inner.get_endpoint(client, service_type, filters).await
136+
}
137+
138+
/// Refresh the cached token and service catalog.
139+
async fn refresh(&self, client: &Client) -> Result<(), Error> {
140+
self.inner.refresh(client, true).await
141+
}
142+
}
143+
144+
#[cfg(test)]
145+
pub mod test {
146+
#![allow(unused_results)]
147+
148+
use reqwest::Url;
149+
150+
use super::ApplicationCredential;
151+
152+
#[test]
153+
fn test_identity_new() {
154+
let id = ApplicationCredential::new("http://127.0.0.1:8080/", "abcdef", "shhhh").unwrap();
155+
let e = Url::parse(id.inner.token_endpoint()).unwrap();
156+
assert_eq!(e.scheme(), "http");
157+
assert_eq!(e.host_str().unwrap(), "127.0.0.1");
158+
assert_eq!(e.port().unwrap(), 8080u16);
159+
assert_eq!(e.path(), "/v3/auth/tokens");
160+
}
161+
}

0 commit comments

Comments
 (0)