1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
/// The configuration for the Rauthy setup.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RauthyConfig {
/// Sets the .is_admin field for the principal based on the `ClaimMapping`.
pub admin_claim: ClaimMapping,
/// Sets the .is_user field for the principal based on the `ClaimMapping`.
/// Without this claim, a user would not have access to this app. This is
/// used, because usually you never want to just have all your OIDC users to
/// have access to a certain application.
pub user_claim: ClaimMapping,
/// In almost all cases, this should just match the `client_id`
pub allowed_audiences: HashSet<String>,
/// the `client_id` from Rauthy
pub client_id: String,
/// If set to 'false', tokens with a non-verified email address will be rejected.
pub email_verified: bool,
/// The issuer URL from your Rauthy deployment
pub iss: String,
/// The scopes you want to request. The only mandatory which always needs to exist is
/// `openid`, the rest is optional and depending on your needs.
pub scope: Vec<String>,
/// If set to None, the client will be treated as a public client and not provide any
/// secret to the /token endpoint after the callback. Set a secret for confidential clients.
pub secret: Option<String>,
/// The Bearer token used for all SCIM operations.
#[cfg(feature = "scim")]
pub scim_token: String,
}
/// The claim selector which decides how a claim should be evaluated, based on the roles and groups.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ClaimMapping {
/// It doesn't matter which role or group a user have, it returns always `true`.
Any,
/// Return true if one of the `JwtClaim` is in the role or group, based on the `JwtClaimTyp`.
Or(Vec<JwtClaim>),
/// Return true if all the `JwtClaim`s are in the role or group, based on the `JwtClaimTyp`.
And(Vec<JwtClaim>),
/// It doesn't matter which role or group a user have, it returns always `false`.
None,
}
impl ClaimMapping {
pub fn matches(&self, roles: &[String], groups: &[String]) -> bool {
match self {
ClaimMapping::Any => true,
ClaimMapping::Or(claims) => claims.iter().any(|claim| claim.matches(roles, groups)),
ClaimMapping::And(claims) => claims.iter().all(|claim| claim.matches(roles, groups)),
ClaimMapping::None => false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtClaim {
pub typ: JwtClaimTyp,
pub value: String,
}
impl JwtClaim {
pub fn matches(&self, roles: &[String], groups: &[String]) -> bool {
match &self.typ {
JwtClaimTyp::Roles => roles.contains(&self.value),
JwtClaimTyp::Groups => groups.contains(&self.value),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum JwtClaimTyp {
Roles,
Groups,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rauthy_error::RauthyError;
use std::ops::Deref;
#[test]
fn test_claim_mapping() -> Result<(), RauthyError> {
let mapping = ClaimMapping::None;
// no matter which roles / group we have - None should always deny access
assert!(!mapping.matches(&vec!["".to_string()], &vec!["".to_string()]));
let mapping = ClaimMapping::Any;
// no matter which roles / group we have - Any should always allow access
assert!(mapping.matches(&vec!["".to_string()], &vec!["".to_string()]));
let test_roles = vec![
"role1".to_string(),
"role2".to_string(),
"role3".to_string(),
];
let test_groups = vec![
"group1".to_string(),
"group2".to_string(),
"group3".to_string(),
];
// AND: these should be allowed
let mapping = ClaimMapping::And(vec![
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role1".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group1".to_string(),
},
]);
assert!(mapping.matches(test_roles.deref(), test_groups.deref()));
let mapping = ClaimMapping::And(vec![
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role1".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role2".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group3".to_string(),
},
]);
assert!(mapping.matches(test_roles.deref(), test_groups.deref()));
// AND: these should not be allowed
let mapping = ClaimMapping::And(vec![
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role1".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group4".to_string(),
},
]);
assert!(!mapping.matches(test_roles.deref(), test_groups.deref()));
let mapping = ClaimMapping::And(vec![
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role4".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group2".to_string(),
},
]);
assert!(!mapping.matches(test_roles.deref(), test_groups.deref()));
// OR: should be allowed
let mapping = ClaimMapping::Or(vec![
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role1".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role4".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group1".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group5".to_string(),
},
]);
assert!(mapping.matches(test_roles.deref(), test_groups.deref()));
// OR: should not be allowed
let mapping = ClaimMapping::Or(vec![
JwtClaim {
typ: JwtClaimTyp::Roles,
value: "role5".to_string(),
},
JwtClaim {
typ: JwtClaimTyp::Groups,
value: "group4".to_string(),
},
]);
assert!(!mapping.matches(test_roles.deref(), test_groups.deref()));
Ok(())
}
}