Add default clients (#105)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
* Add the possibility to create client enabled by default when creating new accounts * Can mark clients are granted for all users, regardless of users accounts grants Reviewed-on: #105
This commit is contained in:
parent
f1ac19cca1
commit
6d2e52d632
693
Cargo.lock
generated
693
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -20,7 +20,7 @@ serde = { version = "1.0.159", features = ["derive"] }
|
||||
bcrypt = "0.14.0"
|
||||
uuid = { version = "1.2.2", features = ["v4"] }
|
||||
mime_guess = "2.0.4"
|
||||
askama = "0.11.1"
|
||||
askama = "0.12.0"
|
||||
futures-util = "0.3.27"
|
||||
urlencoding = "2.1.2"
|
||||
rand = "0.8.5"
|
||||
|
26
README.md
26
README.md
@ -16,6 +16,10 @@ You can configure a list of clients (Relying Parties) in a `clients.yaml` file w
|
||||
description: Git with a cup of tea
|
||||
secret: TOP_SECRET
|
||||
redirect_uri: https://mygit.mywebsite.com/
|
||||
# If you want new accounts to be granted access to this client by default
|
||||
default: true
|
||||
# If you want the client to be granted to every users, regardless their account configuration
|
||||
granted_to_all_users: true
|
||||
```
|
||||
|
||||
On the first run, BasicOIDC will create a new administrator with credentials `admin` / `admin`. On first login you will have to change these default credentials.
|
||||
@ -38,5 +42,27 @@ You will need the Rust toolchain to compile this project. To build it for produc
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
## Testing with OAauth proxy
|
||||
If you want to test the solution with OAuth proxy, you can try to adapt the following commands (considering `192.168.2.103` is your local IP address):
|
||||
|
||||
```bash
|
||||
# In a shell, start BasicOID
|
||||
RUST_LOG=debug cargo run -- -s storage -w "http://192.168.2.103.nip.io:8000"
|
||||
|
||||
# In another shell, run OAuth proxy
|
||||
docker run --rm -p 4180:4180 quay.io/oauth2-proxy/oauth2-proxy:latest --provider=oidc --email-domain=* --client-id=oauthproxy --client-secret=secretoauth --cookie-secret=SECRETCOOKIE1234 --oidc-issuer-url=http://192.168.2.103.nip.io:8000 --http-address 0.0.0.0:4180 --upstream http://192.168.2.103 --redirect-url http://192.168.2.103:4180/oauth2/callback --cookie-secure=false
|
||||
```
|
||||
|
||||
Corresponding client configuration:
|
||||
```yaml
|
||||
- id: oauthproxy
|
||||
name: Oauth proxy
|
||||
description: oauth proxy
|
||||
secret: secretoauth
|
||||
redirect_uri: http://192.168.2.103:4180/
|
||||
```
|
||||
|
||||
> Note: We do need to use real domain name instead of IP address due to the `webauthn-rs` crate limitations. We therefore use the `nip.io` domain helper.
|
||||
|
||||
## Contributing
|
||||
If you wish to contribute to this software, feel free to send an email to contact@communiquons.org to get an account on my system, managed by BasicOIDC :)
|
||||
|
@ -65,7 +65,7 @@ pub struct UpdateUserQuery {
|
||||
}
|
||||
|
||||
pub async fn users_route(
|
||||
user: CurrentUser,
|
||||
admin: CurrentUser,
|
||||
users: web::Data<Addr<UsersActor>>,
|
||||
update_query: Option<web::Form<UpdateUserQuery>>,
|
||||
logger: ActionLogger,
|
||||
@ -225,7 +225,7 @@ pub async fn users_route(
|
||||
|
||||
HttpResponse::Ok().body(
|
||||
UsersListTemplate {
|
||||
_p: BaseSettingsPage::get("Users list", &user, danger, success),
|
||||
_p: BaseSettingsPage::get("Users list", &admin, danger, success),
|
||||
users,
|
||||
}
|
||||
.render()
|
||||
@ -233,11 +233,22 @@ pub async fn users_route(
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn create_user(user: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
|
||||
pub async fn create_user(admin: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
|
||||
let user = User {
|
||||
authorized_clients: Some(
|
||||
clients
|
||||
.get_default_clients()
|
||||
.iter()
|
||||
.map(|u| u.id.clone())
|
||||
.collect(),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
HttpResponse::Ok().body(
|
||||
EditUserTemplate {
|
||||
_p: BaseSettingsPage::get("Create a new user", user.deref(), None, None),
|
||||
u: Default::default(),
|
||||
_p: BaseSettingsPage::get("Create a new user", admin.deref(), None, None),
|
||||
u: user,
|
||||
clients: clients.cloned(),
|
||||
}
|
||||
.render()
|
||||
@ -251,7 +262,7 @@ pub struct EditUserQuery {
|
||||
}
|
||||
|
||||
pub async fn edit_user(
|
||||
user: CurrentUser,
|
||||
admin: CurrentUser,
|
||||
clients: web::Data<ClientManager>,
|
||||
users: web::Data<Addr<UsersActor>>,
|
||||
query: web::Query<EditUserQuery>,
|
||||
@ -266,7 +277,7 @@ pub async fn edit_user(
|
||||
EditUserTemplate {
|
||||
_p: BaseSettingsPage::get(
|
||||
"Edit user account",
|
||||
user.deref(),
|
||||
admin.deref(),
|
||||
match edited_account.is_none() {
|
||||
true => Some("Could not find requested user!".to_string()),
|
||||
false => None,
|
||||
|
@ -164,7 +164,7 @@ pub async fn authorize(
|
||||
};
|
||||
|
||||
// Check if user is authorized to access the application
|
||||
if !user.can_access_app(&client.id) {
|
||||
if !user.can_access_app(&client) {
|
||||
return error_redirect(
|
||||
&query,
|
||||
"invalid_request",
|
||||
|
@ -6,11 +6,28 @@ pub struct ClientID(pub String);
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct Client {
|
||||
/// The ID of the client
|
||||
pub id: ClientID,
|
||||
|
||||
/// The human-readable name of the client
|
||||
pub name: String,
|
||||
|
||||
/// A short description of the service provided by the client
|
||||
pub description: String,
|
||||
|
||||
/// The secret used by the client to retrieve authenticated users information
|
||||
pub secret: String,
|
||||
|
||||
/// The URI where the users should be redirected once authenticated
|
||||
pub redirect_uri: String,
|
||||
|
||||
/// Specify if the client must be allowed by default for new account
|
||||
#[serde(default = "bool::default")]
|
||||
pub default: bool,
|
||||
|
||||
/// Specify whether a client is granted to all users
|
||||
#[serde(default = "bool::default")]
|
||||
pub granted_to_all_users: bool,
|
||||
}
|
||||
|
||||
impl PartialEq for Client {
|
||||
@ -33,6 +50,13 @@ impl EntityManager<Client> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the list of default clients.
|
||||
///
|
||||
/// i.e. the clients that are granted to new accounts by default
|
||||
pub fn get_default_clients(&self) -> Vec<&Client> {
|
||||
self.iter().filter(|u| u.default).collect()
|
||||
}
|
||||
|
||||
pub fn apply_environment_variables(&mut self) {
|
||||
for c in self.iter_mut() {
|
||||
c.id = ClientID(apply_env_vars(&c.id.0));
|
||||
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::constants::SECOND_FACTOR_EXEMPTION_AFTER_SUCCESSFUL_LOGIN;
|
||||
use crate::data::client::ClientID;
|
||||
use crate::data::client::{Client, ClientID};
|
||||
use crate::data::login_redirect::LoginRedirect;
|
||||
use crate::data::totp_key::TotpKey;
|
||||
use crate::data::webauthn_manager::WebauthnPubKey;
|
||||
@ -170,10 +170,14 @@ impl User {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn can_access_app(&self, id: &ClientID) -> bool {
|
||||
pub fn can_access_app(&self, client: &Client) -> bool {
|
||||
if client.granted_to_all_users {
|
||||
return true;
|
||||
}
|
||||
|
||||
match self.granted_clients() {
|
||||
GrantedClients::AllClients => true,
|
||||
GrantedClients::SomeClients(c) => c.contains(id),
|
||||
GrantedClients::SomeClients(c) => c.contains(&client.id),
|
||||
GrantedClients::NoClient => false,
|
||||
}
|
||||
}
|
||||
|
@ -56,15 +56,24 @@ pub struct WebAuthManager {
|
||||
|
||||
impl WebAuthManager {
|
||||
pub fn init(conf: &AppConfig) -> Self {
|
||||
Self {
|
||||
core: WebauthnBuilder::new(
|
||||
conf.domain_name()
|
||||
let rp_id = conf
|
||||
.domain_name()
|
||||
.split_once(':')
|
||||
.map(|s| s.0)
|
||||
.unwrap_or_else(|| conf.domain_name()),
|
||||
&url::Url::parse(&conf.website_origin)
|
||||
.expect("Failed to parse configuration origin!"),
|
||||
)
|
||||
.unwrap_or_else(|| conf.domain_name());
|
||||
|
||||
let rp_origin =
|
||||
url::Url::parse(&conf.website_origin).expect("Failed to parse configuration origin!");
|
||||
|
||||
log::debug!(
|
||||
"rp_id={} rp_origin={} rp_origin_domain={:?}",
|
||||
rp_id,
|
||||
rp_origin,
|
||||
rp_origin.domain()
|
||||
);
|
||||
|
||||
Self {
|
||||
core: WebauthnBuilder::new(rp_id, &rp_origin)
|
||||
.expect("Invalid Webauthn configuration")
|
||||
.rp_name(APP_NAME)
|
||||
.build()
|
||||
|
@ -8,6 +8,8 @@
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Description</th>
|
||||
<th scope="col">Redirect URI</th>
|
||||
<th scope="col">Default</th>
|
||||
<th scope="col">Granted to all users</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -17,6 +19,8 @@
|
||||
<td>{{ c.name }}</td>
|
||||
<td>{{ c.description }}</td>
|
||||
<td>{{ c.redirect_uri }}</td>
|
||||
<td>{% if c.default %}YES{% else %}NO{% endif %}</td>
|
||||
<td>{% if c.granted_to_all_users %}YES{% else %}NO{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
@ -144,7 +144,7 @@
|
||||
<div class="form-check">
|
||||
<input id="client-{{ c.id.0 }}" class="form-check-input authorize_client_checkbox" type="checkbox"
|
||||
data-id="{{ c.id.0 }}"
|
||||
{% if u.can_access_app(c.id) %} checked="" {% endif %}>
|
||||
{% if u.can_access_app(c) %} checked="" {% endif %}>
|
||||
<label class="form-check-label" for="client-{{ c.id.0 }}">
|
||||
{{ c.name }}
|
||||
</label>
|
||||
|
Loading…
Reference in New Issue
Block a user