rs_keycloak/client/
openid.rs

1use serde_json::{Value, json};
2use super::urls::OPENID_URL;
3use minreq::{post, Error};
4use crate::types::Token;
5
6#[derive(Debug)]
7pub struct OpenID {
8    server_url: String,
9    client_id: String,
10    client_secret: String,
11    pub token: Token,
12    roles: Vec<String>,
13}
14
15impl OpenID {
16    fn get_realm_roles(token: &Token) -> Vec<String>{
17        match token {
18            Token::Introspect(e) => {
19                match e.get("realm_access") {
20                    Some(ee) => {
21                        let token_roles = ee.get("roles")
22                        .unwrap()
23                        .as_array()
24                        .unwrap();
25
26                        token_roles.iter()
27                        .map(|i| i.to_string().trim_matches('\"').to_string())
28                        .collect::<Vec<String>>()
29                    },
30                    None => vec![],
31                }
32            },
33            _ => vec![]
34        }
35    }
36
37    pub fn introspect(server_url: &str, realm_name: &str, client_id: &str, client_secret: &str, access_token: &str) -> Result<Self, Error> {
38        let path = OPENID_URL.introspect.replace("{realm-name}", realm_name);
39
40        let url = format!(
41            "{}/{}",
42            server_url,
43            path,
44        );
45
46        let res = post(url)
47            .with_header("Content-Type", "application/x-www-form-urlencoded")
48            .with_body(
49                format!(
50                    "client_id={}&client_secret={}&token={}",
51                    client_id,
52                    client_secret,
53                    access_token,
54                )
55            ).send();
56
57        match res {
58            Ok(e) => {
59                if e.status_code != 200 {
60                    return Err(Error::Other("Unauthorized"));
61                }
62
63                let token = Token::Introspect(e.clone().json().unwrap());
64
65                let realm_roles = Self::get_realm_roles(&token);
66
67                Ok(Self {
68                    server_url: server_url.to_string(),
69                    client_id: client_id.to_string(),
70                    client_secret: client_secret.to_string(),
71                    token: token,
72                    roles: realm_roles,
73                })
74            },
75            Err(e) => Err(e),
76        }
77    }
78
79    pub fn login_with_client(server_url: &str, realm_name: &str, client_id: &str, client_secret: &str) -> Result<Self, Error> {
80        let path = OPENID_URL.token.replace("{realm-name}", realm_name);
81
82        let url = format!(
83            "{}/{}",
84            server_url,
85            path,
86        );
87
88        let res = post(url)
89            .with_header("Content-Type", "application/x-www-form-urlencoded")
90            .with_body(
91                format!(
92                    "client_id={}&client_secret={}&grant_type=client_credentials",
93                    client_id,
94                    client_secret,
95                )
96            ).send();
97
98        match res {
99            Ok(e) => {
100                if e.status_code != 200 {
101                    return Err(Error::Other("Unauthorized"));
102                }
103
104                let token = Token::Client(e.clone().json().unwrap());
105
106                Ok(Self {
107                    server_url: server_url.to_string(),
108                    client_id: client_id.to_string(),
109                    client_secret: client_secret.to_string(),
110                    token: token,
111                    roles: vec![],
112                })
113            },
114            Err(e) => Err(e),
115        }
116    }
117
118    pub fn login_with_password(server_url: &str, realm_name: &str, client_id: &str, client_secret: &str, username: &str, password: &str) -> Result<Self, Error> {
119        let path = OPENID_URL.token.replace("{realm-name}", realm_name);
120
121        let url = format!(
122            "{}/{}",
123            server_url,
124            path,
125        );
126
127        let res = post(url)
128            .with_header("Content-Type", "application/x-www-form-urlencoded")
129            .with_body(
130                format!(
131                    "scope=openid&client_id={}&client_secret={}&grant_type=password&username={}&password={}",
132                    client_id,
133                    client_secret,
134                    username,
135                    password,
136                )
137            ).send();
138
139        match res {
140            Ok(e) => {
141                if e.status_code != 200 {
142                    return Err(Error::Other("Unauthorized"));
143                }
144
145                let token = Token::Password(e.clone().json().unwrap());
146
147                Ok(Self {
148                    server_url: server_url.to_string(),
149                    client_id: client_id.to_string(),
150                    client_secret: client_secret.to_string(),
151                    token: token,
152                    roles: vec![],
153                })
154            },
155            Err(e) => Err(e),
156        }
157    }
158
159    pub fn logout(server_url: &str, realm_name: &str, client_id: &str, client_secret: &str, refresh_token: &str) -> Result<(), Error> {
160        let path = OPENID_URL.logout.replace("{realm-name}", realm_name);
161
162        let url = format!(
163            "{}/{}",
164            server_url,
165            path,
166        );
167
168        let res = post(url)
169            .with_header("Content-Type", "application/x-www-form-urlencoded")
170            .with_body(
171                format!(
172                    "client_id={}&client_secret={}&refresh_token={}",
173                    client_id,
174                    client_secret,
175                    refresh_token,
176                )
177            ).send();
178
179        match res {
180            Ok(e) => {
181                if e.status_code != 204 {
182                    return Err(Error::Other("Unauthorized"));
183                }
184
185                Ok(())
186            },
187            Err(e) => Err(e),
188        }
189    }
190
191    pub fn get_token_type(&self) -> String {
192        match &self.token {
193            Token::Client(token) => token.token_type.to_string(),
194            Token::Password(token) => token.token_type.to_string(),
195            _ => "".to_string(),
196        }
197    }
198
199    pub fn get_access_token(&self) -> String {
200        match &self.token {
201            Token::Client(token) => token.access_token.to_string(),
202            Token::Password(token) => token.access_token.to_string(),
203            _ => "".to_string(),
204        }
205    }
206
207    pub fn get_refresh_token(&self) -> Option<String> {
208        match &self.token {
209            Token::Client(_) => None,
210            Token::Password(token) => Some(token.refresh_token.to_string()),
211            _ => None,
212        }
213    }
214
215    pub fn get_expires_in(&self) -> u64 {
216        match &self.token {
217            Token::Client(token) => token.expires_in,
218            Token::Password(token) => token.expires_in,
219            _ => 0,
220        }
221    }
222
223    pub fn get_refresh_expires_in(&self) -> u64 {
224        match &self.token {
225            Token::Client(token) => token.refresh_expires_in,
226            Token::Password(token) => token.refresh_expires_in,
227            _ => 0,
228        }
229    }
230
231    pub fn get_scopes(&self) -> Vec<&str> {
232        match &self.token {
233            Token::Client(token) => token.scope.split(' ').collect::<Vec<&str>>(),
234            Token::Password(token) => token.scope.split(' ').collect::<Vec<&str>>(),
235            _ => vec![],
236        }
237    }
238
239    pub fn get_roles(&self) -> &Vec<String> {
240        &self.roles
241    }
242
243    pub fn has_any_roles(&self, roles: &[&str]) -> bool {
244        for role in &self.roles {
245            if roles.contains(&role.as_str()) {
246                return true;
247            }
248        }
249
250        false
251    }
252
253    pub fn has_all_roles(&self, roles: &[&str]) -> bool {
254        let roles_count = roles.len();
255
256        let mut roles_found: usize = 0;
257
258        for role in &self.roles {
259            if roles.contains(&role.as_str()) {
260                roles_found += 1;
261            }
262        }
263
264        roles_count == roles_found
265    }
266
267    pub fn get_decoded_token(&self) -> Value {
268        match &self.token {
269            Token::Introspect(e) => e.clone(),
270            _ => json!({}),
271        }
272    }
273}