Skip to content

Commit 3020fc1

Browse files
committed
Delegate all auth logic to OPA
Requires new rules on central OPA instance but allows all logic to be reduced to a single yes/no response.
1 parent 5149a28 commit 3020fc1

File tree

2 files changed

+22
-77
lines changed

2 files changed

+22
-77
lines changed

src/cli.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,26 @@ pub struct ServeOptions {
5858
/// The port to open for requests
5959
#[clap(short, long, default_value_t = 8000, env = "NUMTRACKER_PORT")]
6060
port: u16,
61-
#[clap(flatten)]
61+
#[clap(flatten, next_help_heading = "Authorization")]
6262
pub policy: Option<PolicyOptions>,
6363
}
6464

6565
#[derive(Debug, Default, Parser)]
6666
pub struct PolicyOptions {
6767
/// Beamline Policy Endpoint
6868
///
69-
/// eg, authz.diamond.ac.uk
69+
/// eg, https://authz.diamond.ac.uk
7070
#[clap(long = "policy")]
71-
pub host: String,
72-
/// The Rego query used to generate visit access data
71+
pub policy_host: String,
72+
/// The Rego rule used to generate visit access data
7373
///
74-
/// eg.
75-
/// access:=data.diamond.policy.session.access;beamline_match:=data.diamond.policy.session.beamline_matches
76-
#[clap(long, requires = "policy")]
74+
/// eg. v1/data/diamond/policy/session/write_to_beamline_visit
75+
#[clap(long)]
7776
pub visit_query: String,
78-
/// The Rego query used to generate admin access data
77+
/// The Rego rule used to generate admin access data
7978
///
80-
/// eg. admin:=data.diamond.policy.admin.admin;beamline:=data.diamond.policy.admin.beamline_admin
81-
#[clap(long, requires = "policy")]
79+
/// eg. v1/data/diamond/policy/admin/configure_beamline
80+
#[clap(long)]
8281
pub admin_query: String,
8382
}
8483

src/graphql/auth.rs

Lines changed: 13 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use access::Request as AccessReq;
55
use admin::Request as AdminReq;
66
use axum_extra::headers::authorization::Bearer;
77
use axum_extra::headers::Authorization;
8-
use serde::de::DeserializeOwned;
98
use serde::{Deserialize, Serialize};
109

1110
use crate::cli::PolicyOptions;
@@ -14,28 +13,15 @@ const AUDIENCE: &str = "account";
1413

1514
type Token = Authorization<Bearer>;
1615

17-
#[derive(Debug, Serialize)]
18-
struct Query<'q, I> {
19-
query: &'q str,
20-
input: I,
21-
}
22-
2316
#[derive(Debug, Deserialize)]
24-
struct Response<R> {
25-
result: R,
26-
}
27-
28-
trait Input: Serialize {
29-
type Response: Auth;
30-
}
31-
trait Auth: DeserializeOwned {
32-
fn check(self) -> Result<(), AuthError>;
17+
struct Response {
18+
result: bool,
3319
}
3420

3521
mod access {
3622
use serde::{Deserialize, Serialize};
3723

38-
use super::{Auth, AuthError, Input, Token, Visit, AUDIENCE};
24+
use super::{AuthError, Token, Visit, AUDIENCE};
3925

4026
#[derive(Debug, Serialize)]
4127
pub struct Request<'a> {
@@ -68,27 +54,12 @@ mod access {
6854
}
6955
}
7056

71-
impl Input for Request<'_> {
72-
type Response = Decision;
73-
}
74-
75-
impl Auth for Decision {
76-
fn check(self) -> Result<(), super::AuthError> {
77-
if !self.access {
78-
Err(AuthError::Failed)
79-
} else if !self.beamline_matches {
80-
Err(AuthError::BeamlineMismatch)
81-
} else {
82-
Ok(())
83-
}
84-
}
85-
}
8657
}
8758

8859
mod admin {
8960
use serde::{Deserialize, Serialize};
9061

91-
use super::{Auth, AuthError, Input, Token, AUDIENCE};
62+
use super::{AuthError, Token, AUDIENCE};
9263

9364
#[derive(Debug, Serialize)]
9465
pub struct Request<'a> {
@@ -103,10 +74,6 @@ mod admin {
10374
admin: bool,
10475
}
10576

106-
impl Input for Request<'_> {
107-
type Response = Decision;
108-
}
109-
11077
impl<'r> Request<'r> {
11178
pub(crate) fn new(token: Option<&'r Token>, beamline: &'r str) -> Result<Self, AuthError> {
11279
Ok(Self {
@@ -117,15 +84,6 @@ mod admin {
11784
}
11885
}
11986

120-
impl Auth for Decision {
121-
fn check(self) -> Result<(), AuthError> {
122-
if self.beamline_admin || self.admin {
123-
Ok(())
124-
} else {
125-
Err(AuthError::Failed)
126-
}
127-
}
128-
}
12987
}
13088

13189
#[derive(Debug)]
@@ -151,8 +109,6 @@ impl FromStr for Visit {
151109

152110
pub(crate) struct PolicyCheck {
153111
client: reqwest::Client,
154-
/// OPA instance
155-
host: String,
156112
/// Rego query for getting admin rights
157113
admin: String,
158114
/// Rego query for getting access rights
@@ -163,9 +119,8 @@ impl PolicyCheck {
163119
pub fn new(endpoint: PolicyOptions) -> Self {
164120
Self {
165121
client: reqwest::Client::new(),
166-
host: endpoint.host + "/v1/query",
167-
admin: endpoint.admin_query,
168-
access: endpoint.visit_query,
122+
admin: format!("{}/{}", endpoint.policy_host, endpoint.admin_query),
123+
access: format!("{}/{}", endpoint.policy_host, &endpoint.visit_query),
169124
}
170125
}
171126
pub async fn check_access(
@@ -188,26 +143,20 @@ impl PolicyCheck {
188143
.await
189144
}
190145

191-
async fn authorise<'q, I: Input>(&self, query: &'q str, input: I) -> Result<(), AuthError> {
192-
let response = self
193-
.client
194-
.post(&self.host)
195-
.json(&Query { query, input })
196-
.send()
197-
.await?;
198-
response
199-
.json::<Response<I::Response>>()
200-
.await?
201-
.result
202-
.check()
146+
async fn authorise<'q>(&self, query: &'q str, input: impl Serialize) -> Result<(), AuthError> {
147+
let response = self.client.post(query).json(&input).send().await?;
148+
if response.json::<Response>().await?.result {
149+
Ok(())
150+
} else {
151+
Err(AuthError::Failed)
152+
}
203153
}
204154
}
205155

206156
#[derive(Debug)]
207157
pub enum AuthError {
208158
ServerError(reqwest::Error),
209159
Failed,
210-
BeamlineMismatch,
211160
Missing,
212161
}
213162

@@ -216,9 +165,6 @@ impl Display for AuthError {
216165
match self {
217166
AuthError::ServerError(e) => e.fmt(f),
218167
AuthError::Failed => write!(f, "Authentication failed"),
219-
AuthError::BeamlineMismatch => {
220-
f.write_str("Invalid beamline. Visit is not on current beamline")
221-
}
222168
AuthError::Missing => f.write_str("No authenication token was provided"),
223169
}
224170
}

0 commit comments

Comments
 (0)