Skip to content

Commit b178a05

Browse files
authored
Allow users to fetch own role(s) (#473)
1 parent 1e7d2be commit b178a05

File tree

6 files changed

+95
-74
lines changed

6 files changed

+95
-74
lines changed

server/src/handlers/http.rs

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::fs::File;
2020
use std::io::BufReader;
2121

2222
use actix_cors::Cors;
23-
use actix_web::{web, App, HttpServer, Route};
23+
use actix_web::{web, App, HttpServer};
2424
use actix_web_prometheus::PrometheusMetrics;
2525
use actix_web_static_files::ResourceFiles;
2626
use rustls::{Certificate, PrivateKey, ServerConfig};
@@ -29,7 +29,7 @@ use rustls_pemfile::{certs, pkcs8_private_keys};
2929
use crate::option::CONFIG;
3030
use crate::rbac::role::Action;
3131

32-
use self::middleware::{Auth, DisAllowRootUser};
32+
use self::middleware::{DisAllowRootUser, RouteExt};
3333

3434
mod about;
3535
mod health_check;
@@ -201,17 +201,25 @@ pub fn configure_routes(cfg: &mut web::ServiceConfig) {
201201
web::delete()
202202
.to(rbac::delete_user)
203203
.authorize(Action::DeleteUser),
204-
),
204+
)
205+
.wrap(DisAllowRootUser),
205206
)
206207
.service(
207208
web::resource("/{username}/role")
208209
// PUT /user/{username}/roles => Put roles for user
209-
.route(web::put().to(rbac::put_role).authorize(Action::PutRoles))
210-
.route(web::get().to(rbac::get_role).authorize(Action::GetRole)),
211-
)
212-
// Deny request if username is same as the env variable P_USERNAME.
213-
.wrap(DisAllowRootUser);
214-
210+
.route(
211+
web::put()
212+
.to(rbac::put_role)
213+
.authorize(Action::PutRoles)
214+
.wrap(DisAllowRootUser),
215+
)
216+
.route(
217+
web::get()
218+
.to(rbac::get_role)
219+
.authorize_for_user(Action::GetRole),
220+
),
221+
);
222+
// Deny request if username is same as the env variable P_USERNAME.
215223
cfg.service(
216224
// Base path "{url}/api/v1"
217225
web::scope(&base_path())
@@ -261,24 +269,3 @@ fn base_path() -> String {
261269
pub fn metrics_path() -> String {
262270
format!("{}/metrics", base_path())
263271
}
264-
265-
trait RouteExt {
266-
fn authorize(self, action: Action) -> Self;
267-
fn authorize_for_stream(self, action: Action) -> Self;
268-
}
269-
270-
impl RouteExt for Route {
271-
fn authorize(self, action: Action) -> Self {
272-
self.wrap(Auth {
273-
action,
274-
stream: false,
275-
})
276-
}
277-
278-
fn authorize_for_stream(self, action: Action) -> Self {
279-
self.wrap(Auth {
280-
action,
281-
stream: true,
282-
})
283-
}
284-
}

server/src/handlers/http/middleware.rs

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,46 @@ use std::future::{ready, Ready};
2222
use actix_web::{
2323
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
2424
error::{ErrorBadRequest, ErrorUnauthorized},
25-
Error,
25+
Error, Route,
2626
};
2727
use actix_web_httpauth::extractors::basic::BasicAuth;
2828
use futures_util::future::LocalBoxFuture;
2929

3030
use crate::{option::CONFIG, rbac::role::Action, rbac::Users};
3131

32+
pub trait RouteExt {
33+
fn authorize(self, action: Action) -> Self;
34+
fn authorize_for_stream(self, action: Action) -> Self;
35+
fn authorize_for_user(self, action: Action) -> Self;
36+
}
37+
38+
impl RouteExt for Route {
39+
fn authorize(self, action: Action) -> Self {
40+
self.wrap(Auth {
41+
action,
42+
method: auth_no_context,
43+
})
44+
}
45+
46+
fn authorize_for_stream(self, action: Action) -> Self {
47+
self.wrap(Auth {
48+
action,
49+
method: auth_stream_context,
50+
})
51+
}
52+
53+
fn authorize_for_user(self, action: Action) -> Self {
54+
self.wrap(Auth {
55+
action,
56+
method: auth_user_context,
57+
})
58+
}
59+
}
60+
61+
// Authentication Layer with no context
3262
pub struct Auth {
3363
pub action: Action,
34-
pub stream: bool,
64+
pub method: fn(&mut ServiceRequest, Action) -> Result<bool, Error>,
3565
}
3666

3767
impl<S, B> Transform<S, ServiceRequest> for Auth
@@ -49,15 +79,15 @@ where
4979
fn new_transform(&self, service: S) -> Self::Future {
5080
ready(Ok(AuthMiddleware {
5181
action: self.action,
52-
match_stream: self.stream,
5382
service,
83+
auth_method: self.method,
5484
}))
5585
}
5686
}
5787

5888
pub struct AuthMiddleware<S> {
5989
action: Action,
60-
match_stream: bool,
90+
auth_method: fn(&mut ServiceRequest, Action) -> Result<bool, Error>,
6191
service: S,
6292
}
6393

@@ -74,38 +104,46 @@ where
74104
forward_ready!(service);
75105

76106
fn call(&self, mut req: ServiceRequest) -> Self::Future {
77-
// Extract username and password from the request using basic auth extractor.
78-
let creds = req.extract::<BasicAuth>().into_inner();
79-
let creds = creds.map_err(Into::into).map(|creds| {
80-
let username = creds.user_id().trim().to_owned();
81-
// password is not mandatory by basic auth standard.
82-
// If not provided then treat as empty string
83-
let password = creds.password().unwrap_or("").trim().to_owned();
84-
(username, password)
85-
});
86-
87-
let stream = if self.match_stream {
88-
req.match_info().get("logstream")
89-
} else {
90-
None
91-
};
92-
93-
let auth_result: Result<bool, Error> = creds.map(|(username, password)| {
94-
Users.authenticate(username, password, self.action, stream)
95-
});
96-
107+
let auth_result: Result<bool, Error> = (self.auth_method)(&mut req, self.action);
97108
let fut = self.service.call(req);
98-
99109
Box::pin(async move {
100110
if !auth_result? {
101111
return Err(ErrorUnauthorized("Not authorized"));
102112
}
103-
104113
fut.await
105114
})
106115
}
107116
}
108117

118+
pub fn auth_no_context(req: &mut ServiceRequest, action: Action) -> Result<bool, Error> {
119+
let creds = extract_basic_auth(req);
120+
creds.map(|(username, password)| Users.authenticate(username, password, action, None, None))
121+
}
122+
123+
pub fn auth_stream_context(req: &mut ServiceRequest, action: Action) -> Result<bool, Error> {
124+
let creds = extract_basic_auth(req);
125+
let stream = req.match_info().get("logstream");
126+
creds.map(|(username, password)| Users.authenticate(username, password, action, stream, None))
127+
}
128+
129+
pub fn auth_user_context(req: &mut ServiceRequest, action: Action) -> Result<bool, Error> {
130+
let creds = extract_basic_auth(req);
131+
let user = req.match_info().get("username");
132+
creds.map(|(username, password)| Users.authenticate(username, password, action, None, user))
133+
}
134+
135+
fn extract_basic_auth(req: &mut ServiceRequest) -> Result<(String, String), Error> {
136+
// Extract username and password from the request using basic auth extractor.
137+
let creds = req.extract::<BasicAuth>().into_inner();
138+
creds.map_err(Into::into).map(|creds| {
139+
let username = creds.user_id().trim().to_owned();
140+
// password is not mandatory by basic auth standard.
141+
// If not provided then treat as empty string
142+
let password = creds.password().unwrap_or("").trim().to_owned();
143+
(username, password)
144+
})
145+
}
146+
109147
// The credentials set in the env vars (P_USERNAME & P_PASSWORD) are treated
110148
// as root credentials. Any other user is not allowed to modify or delete
111149
// the root user. Deny request if username is same as username

server/src/handlers/http/rbac.rs

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ pub async fn put_user(
5151
) -> Result<impl Responder, RBACError> {
5252
let username = username.into_inner();
5353
validator::user_name(&username)?;
54-
if username == CONFIG.parseable.username {
55-
return Err(RBACError::BadUser);
56-
}
5754
let _ = UPDATE_LOCK.lock().await;
5855
if Users.contains(&username) {
5956
reset_password(username).await
@@ -90,9 +87,6 @@ pub async fn get_role(username: web::Path<String>) -> Result<impl Responder, RBA
9087
// Handler for DELETE /api/v1/user/delete/{username}
9188
pub async fn delete_user(username: web::Path<String>) -> Result<impl Responder, RBACError> {
9289
let username = username.into_inner();
93-
if username == CONFIG.parseable.username {
94-
return Err(RBACError::BadUser);
95-
}
9690
let _ = UPDATE_LOCK.lock().await;
9791
// fail this request if the user does not exists
9892
if !Users.contains(&username) {
@@ -137,9 +131,6 @@ pub async fn put_role(
137131
role: web::Json<serde_json::Value>,
138132
) -> Result<String, RBACError> {
139133
let username = username.into_inner();
140-
if username == CONFIG.parseable.username {
141-
return Err(RBACError::BadUser);
142-
}
143134
let role = role.into_inner();
144135
let role: HashSet<DefaultPrivilege> = serde_json::from_value(role)?;
145136
let role = role.into_iter().collect();
@@ -184,8 +175,6 @@ async fn put_metadata(metadata: &StorageMetadata) -> Result<(), ObjectStorageErr
184175

185176
#[derive(Debug, thiserror::Error)]
186177
pub enum RBACError {
187-
#[error("Request cannot be allowed for this user")]
188-
BadUser,
189178
#[error("User exists already")]
190179
UserExists,
191180
#[error("User does not exist")]
@@ -201,7 +190,6 @@ pub enum RBACError {
201190
impl actix_web::ResponseError for RBACError {
202191
fn status_code(&self) -> http::StatusCode {
203192
match self {
204-
Self::BadUser => StatusCode::BAD_REQUEST,
205193
Self::UserExists => StatusCode::BAD_REQUEST,
206194
Self::UserDoesNotExist => StatusCode::NOT_FOUND,
207195
Self::SerdeError(_) => StatusCode::BAD_REQUEST,

server/src/rbac.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,12 @@ impl Users {
8080
username: String,
8181
password: String,
8282
action: Action,
83-
stream: Option<&str>,
83+
context_stream: Option<&str>,
84+
context_user: Option<&str>,
8485
) -> bool {
8586
let key = (username, password);
8687
// try fetch from auth map for faster auth flow
87-
if let Some(res) = auth_map().check_auth(&key, action, stream) {
88+
if let Some(res) = auth_map().check_auth(&key, action, context_stream, context_user) {
8889
return res;
8990
}
9091

@@ -99,7 +100,7 @@ impl Users {
99100
// verify from auth map and return
100101
let key = (username, password);
101102
return auth_map
102-
.check_auth(&key, action, stream)
103+
.check_auth(&key, action, context_stream, context_user)
103104
.expect("entry for this key just added");
104105
}
105106
}

server/src/rbac/map.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,8 @@ impl AuthMap {
117117
&self,
118118
key: &(String, String),
119119
required_action: Action,
120-
on_stream: Option<&str>,
120+
context_stream: Option<&str>,
121+
context_user: Option<&str>,
121122
) -> Option<bool> {
122123
self.inner.get(key).map(|perms| {
123124
perms.iter().any(|user_perm| {
@@ -126,14 +127,18 @@ impl AuthMap {
126127
Permission::Unit(action) => action == required_action || action == Action::All,
127128
Permission::Stream(action, ref stream)
128129
| Permission::StreamWithTag(action, ref stream, _) => {
129-
let ok_stream = if let Some(on_stream) = on_stream {
130-
stream == on_stream || stream == "*"
130+
let ok_stream = if let Some(context_stream) = context_stream {
131+
stream == context_stream || stream == "*"
131132
} else {
132133
// if no stream to match then stream check is not needed
133134
true
134135
};
135136
(action == required_action || action == Action::All) && ok_stream
136137
}
138+
Permission::SelfRole => {
139+
context_user.map(|x| x == key.0).unwrap_or_default()
140+
&& required_action == Action::GetRole
141+
}
137142
}
138143
})
139144
})

server/src/rbac/role.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pub enum Permission {
4444
Unit(Action),
4545
Stream(Action, String),
4646
StreamWithTag(Action, String, Option<String>),
47+
SelfRole,
4748
}
4849

4950
// Currently Roles are tied to one stream
@@ -94,6 +95,7 @@ impl RoleBuilder {
9495
};
9596
perms.push(perm);
9697
}
98+
perms.push(Permission::SelfRole);
9799
perms
98100
}
99101
}

0 commit comments

Comments
 (0)