mas_policy/
model.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7//! Input and output types for policy evaluation.
8//!
9//! This is useful to generate JSON schemas for each input type, which can then
10//! be type-checked by Open Policy Agent.
11
12use std::net::IpAddr;
13
14use mas_data_model::{Client, User};
15use oauth2_types::{registration::VerifiedClientMetadata, scope::Scope};
16use serde::{Deserialize, Serialize};
17
18/// A well-known policy code.
19#[derive(Deserialize, Debug, Clone, Copy)]
20#[serde(rename_all = "kebab-case")]
21#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
22pub enum Code {
23    /// The username is too short.
24    UsernameTooShort,
25
26    /// The username is too long.
27    UsernameTooLong,
28
29    /// The username contains invalid characters.
30    UsernameInvalidChars,
31
32    /// The username contains only numeric characters.
33    UsernameAllNumeric,
34
35    /// The username is banned.
36    UsernameBanned,
37
38    /// The username is not allowed.
39    UsernameNotAllowed,
40
41    /// The email domain is not allowed.
42    EmailDomainNotAllowed,
43
44    /// The email domain is banned.
45    EmailDomainBanned,
46
47    /// The email address is not allowed.
48    EmailNotAllowed,
49
50    /// The email address is banned.
51    EmailBanned,
52}
53
54impl Code {
55    /// Returns the code as a string
56    #[must_use]
57    pub fn as_str(&self) -> &'static str {
58        match self {
59            Self::UsernameTooShort => "username-too-short",
60            Self::UsernameTooLong => "username-too-long",
61            Self::UsernameInvalidChars => "username-invalid-chars",
62            Self::UsernameAllNumeric => "username-all-numeric",
63            Self::UsernameBanned => "username-banned",
64            Self::UsernameNotAllowed => "username-not-allowed",
65            Self::EmailDomainNotAllowed => "email-domain-not-allowed",
66            Self::EmailDomainBanned => "email-domain-banned",
67            Self::EmailNotAllowed => "email-not-allowed",
68            Self::EmailBanned => "email-banned",
69        }
70    }
71}
72
73/// A single violation of a policy.
74#[derive(Deserialize, Debug)]
75#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
76pub struct Violation {
77    pub msg: String,
78    pub redirect_uri: Option<String>,
79    pub field: Option<String>,
80    pub code: Option<Code>,
81}
82
83/// The result of a policy evaluation.
84#[derive(Deserialize, Debug)]
85pub struct EvaluationResult {
86    #[serde(rename = "result")]
87    pub violations: Vec<Violation>,
88}
89
90impl std::fmt::Display for EvaluationResult {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92        let mut first = true;
93        for violation in &self.violations {
94            if first {
95                first = false;
96            } else {
97                write!(f, ", ")?;
98            }
99            write!(f, "{}", violation.msg)?;
100        }
101        Ok(())
102    }
103}
104
105impl EvaluationResult {
106    /// Returns true if the policy evaluation was successful.
107    #[must_use]
108    pub fn valid(&self) -> bool {
109        self.violations.is_empty()
110    }
111}
112
113/// Identity of the requester
114#[derive(Serialize, Debug, Default)]
115#[serde(rename_all = "snake_case")]
116#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
117pub struct Requester {
118    /// IP address of the entity making the request
119    pub ip_address: Option<IpAddr>,
120
121    /// User agent of the entity making the request
122    pub user_agent: Option<String>,
123}
124
125#[derive(Serialize, Debug)]
126#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
127pub enum RegistrationMethod {
128    #[serde(rename = "password")]
129    Password,
130
131    #[serde(rename = "upstream-oauth2")]
132    UpstreamOAuth2,
133}
134
135/// Input for the user registration policy.
136#[derive(Serialize, Debug)]
137#[serde(tag = "registration_method")]
138#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
139pub struct RegisterInput<'a> {
140    pub registration_method: RegistrationMethod,
141
142    pub username: &'a str,
143
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub email: Option<&'a str>,
146
147    pub requester: Requester,
148}
149
150/// Input for the client registration policy.
151#[derive(Serialize, Debug)]
152#[serde(rename_all = "snake_case")]
153#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
154pub struct ClientRegistrationInput<'a> {
155    #[cfg_attr(
156        feature = "jsonschema",
157        schemars(with = "std::collections::HashMap<String, serde_json::Value>")
158    )]
159    pub client_metadata: &'a VerifiedClientMetadata,
160    pub requester: Requester,
161}
162
163#[derive(Serialize, Debug)]
164#[serde(rename_all = "snake_case")]
165#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
166pub enum GrantType {
167    AuthorizationCode,
168    ClientCredentials,
169    #[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
170    DeviceCode,
171}
172
173/// Input for the authorization grant policy.
174#[derive(Serialize, Debug)]
175#[serde(rename_all = "snake_case")]
176#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
177pub struct AuthorizationGrantInput<'a> {
178    #[cfg_attr(
179        feature = "jsonschema",
180        schemars(with = "Option<std::collections::HashMap<String, serde_json::Value>>")
181    )]
182    pub user: Option<&'a User>,
183
184    #[cfg_attr(
185        feature = "jsonschema",
186        schemars(with = "std::collections::HashMap<String, serde_json::Value>")
187    )]
188    pub client: &'a Client,
189
190    #[cfg_attr(feature = "jsonschema", schemars(with = "String"))]
191    pub scope: &'a Scope,
192
193    pub grant_type: GrantType,
194
195    pub requester: Requester,
196}
197
198/// Input for the email add policy.
199#[derive(Serialize, Debug)]
200#[serde(rename_all = "snake_case")]
201#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
202pub struct EmailInput<'a> {
203    pub email: &'a str,
204
205    pub requester: Requester,
206}