1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-08-07 17:03:01 +03:00

policy: prepare for the client credentials grant

This commit is contained in:
Quentin Gliech
2023-09-04 18:22:50 +02:00
parent 7a9197f222
commit 8658a3400d
5 changed files with 150 additions and 36 deletions

View File

@@ -20,7 +20,7 @@
pub mod model; pub mod model;
use mas_data_model::{AuthorizationGrant, Client, User}; use mas_data_model::{AuthorizationGrant, Client, User};
use oauth2_types::registration::VerifiedClientMetadata; use oauth2_types::{registration::VerifiedClientMetadata, scope::Scope};
use opa_wasm::Runtime; use opa_wasm::Runtime;
use thiserror::Error; use thiserror::Error;
use tokio::io::{AsyncRead, AsyncReadExt}; use tokio::io::{AsyncRead, AsyncReadExt};
@@ -30,6 +30,7 @@ use self::model::{
AuthorizationGrantInput, ClientRegistrationInput, EmailInput, PasswordInput, RegisterInput, AuthorizationGrantInput, ClientRegistrationInput, EmailInput, PasswordInput, RegisterInput,
}; };
pub use self::model::{EvaluationResult, Violation}; pub use self::model::{EvaluationResult, Violation};
use crate::model::GrantType;
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum LoadError { pub enum LoadError {
@@ -300,6 +301,7 @@ impl Policy {
skip_all, skip_all,
fields( fields(
input.authorization_grant.id = %authorization_grant.id, input.authorization_grant.id = %authorization_grant.id,
input.scope = %authorization_grant.scope,
input.client.id = %client.id, input.client.id = %client.id,
input.user.id = %user.id, input.user.id = %user.id,
), ),
@@ -314,7 +316,43 @@ impl Policy {
let input = AuthorizationGrantInput { let input = AuthorizationGrantInput {
user, user,
client, client,
authorization_grant, scope: &authorization_grant.scope,
grant_type: GrantType::AuthorizationCode,
};
let [res]: [EvaluationResult; 1] = self
.instance
.evaluate(
&mut self.store,
&self.entrypoints.authorization_grant,
&input,
)
.await?;
Ok(res)
}
#[tracing::instrument(
name = "policy.evaluate.client_credentials_grant",
skip_all,
fields(
input.scope = %scope,
input.client.id = %client.id,
input.user.id = %user.id,
),
err,
)]
pub async fn evaluate_client_credentials_grant(
&mut self,
scope: &Scope,
client: &Client,
user: &User,
) -> Result<EvaluationResult, EvaluationError> {
let input = AuthorizationGrantInput {
user,
client,
scope,
grant_type: GrantType::ClientCredentials,
}; };
let [res]: [EvaluationResult; 1] = self let [res]: [EvaluationResult; 1] = self

View File

@@ -12,8 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use mas_data_model::{AuthorizationGrant, Client, User}; //! Input and output types for policy evaluation.
use oauth2_types::registration::VerifiedClientMetadata; //!
//! This is useful to generate JSON schemas for each input type, which can then
//! be type-checked by Open Policy Agent.
use mas_data_model::{Client, User};
use oauth2_types::{registration::VerifiedClientMetadata, scope::Scope};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
/// A single violation of a policy. /// A single violation of a policy.
@@ -87,6 +92,14 @@ pub struct ClientRegistrationInput<'a> {
pub client_metadata: &'a VerifiedClientMetadata, pub client_metadata: &'a VerifiedClientMetadata,
} }
#[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
pub enum GrantType {
AuthorizationCode,
ClientCredentials,
}
/// Input for the authorization grant policy. /// Input for the authorization grant policy.
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@@ -104,11 +117,10 @@ pub struct AuthorizationGrantInput<'a> {
)] )]
pub client: &'a Client, pub client: &'a Client,
#[cfg_attr( #[cfg_attr(feature = "jsonschema", schemars(with = "String"))]
feature = "jsonschema", pub scope: &'a Scope,
schemars(with = "std::collections::HashMap<String, serde_json::Value>")
)] pub grant_type: GrantType,
pub authorization_grant: &'a AuthorizationGrant,
} }
/// Input for the email add policy. /// Input for the email add policy.

View File

@@ -20,6 +20,7 @@ allowed_scope("email") = true
# This grants access to Synapse's admin API endpoints # This grants access to Synapse's admin API endpoints
allowed_scope("urn:synapse:admin:*") { allowed_scope("urn:synapse:admin:*") {
input.grant_type == "authorization_code"
some user in data.admin_users some user in data.admin_users
input.user.username == user input.user.username == user
} }
@@ -29,23 +30,36 @@ allowed_scope("urn:mas:graphql:*") = true
# This makes it possible to query and do anything in the GraphQL API as an admin # This makes it possible to query and do anything in the GraphQL API as an admin
allowed_scope("urn:mas:admin") { allowed_scope("urn:mas:admin") {
input.grant_type == "authorization_code"
some user in data.admin_users some user in data.admin_users
input.user.username == user input.user.username == user
} }
# This makes it possible to get the admin scope for clients that are allowed
allowed_scope("urn:mas:admin") {
input.grant_type == "client_credentials"
some client in data.admin_clients
input.client.id == client
}
allowed_scope(scope) { allowed_scope(scope) {
# Grant access to the C-S API only if there is a user
input.grant_type == "authorization_code"
regex.match("urn:matrix:org.matrix.msc2967.client:device:[A-Za-z0-9-]{10,}", scope) regex.match("urn:matrix:org.matrix.msc2967.client:device:[A-Za-z0-9-]{10,}", scope)
} }
allowed_scope("urn:matrix:org.matrix.msc2967.client:api:*") = true allowed_scope("urn:matrix:org.matrix.msc2967.client:api:*") {
# Grant access to the C-S API only if there is a user
input.grant_type == "authorization_code"
}
violation[{"msg": msg}] { violation[{"msg": msg}] {
some scope in split(input.authorization_grant.scope, " ") some scope in split(input.scope, " ")
not allowed_scope(scope) not allowed_scope(scope)
msg := sprintf("scope '%s' not allowed", [scope]) msg := sprintf("scope '%s' not allowed", [scope])
} }
violation[{"msg": "only one device scope is allowed at a time"}] { violation[{"msg": "only one device scope is allowed at a time"}] {
scope_list := split(input.authorization_grant.scope, " ") scope_list := split(input.scope, " ")
count({key | scope_list[key]; startswith(scope_list[key], "urn:matrix:org.matrix.msc2967.client:device:")}) > 1 count({key | scope_list[key]; startswith(scope_list[key], "urn:matrix:org.matrix.msc2967.client:device:")}) > 1
} }

View File

@@ -2,78 +2,116 @@ package authorization_grant
user := {"username": "john"} user := {"username": "john"}
client := {"client_id": "client"}
test_standard_scopes { test_standard_scopes {
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": ""} with input.client as client
with input.scope as ""
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "openid"} with input.client as client
with input.scope as "openid"
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "email"} with input.client as client
with input.scope as "email"
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "openid email"} with input.client as client
with input.scope as "openid email"
# Not supported yet # Not supported yet
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "phone"} with input.client as client
with input.scope as "phone"
# Not supported yet # Not supported yet
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "profile"} with input.client as client
with input.scope as "profile"
} }
test_matrix_scopes { test_matrix_scopes {
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:api:*"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:api:*"
} }
test_device_scopes { test_device_scopes {
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01"
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01-asdasdsa1-2313"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01-asdasdsa1-2313"
# Invalid characters # Invalid characters
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:AABB:CCDDEE"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AABB:CCDDEE"
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:AABB*CCDDEE"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AABB*CCDDEE"
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:AABB!CCDDEE"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AABB!CCDDEE"
# Too short # Too short
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:abcd"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:abcd"
# Multiple device scope # Multiple device scope
not allow with input.user as user not allow with input.user as user
with input.authorization_grant as {"scope": "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01 urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd02"} with input.client as client
with input.grant_type as "authorization_code"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01 urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd02"
# Not allowed for the client credentials grant
not allow with input.client as client
with input.grant_type as "client_credentials"
with input.scope as "urn:matrix:org.matrix.msc2967.client:device:AAbbCCdd01"
} }
test_synapse_admin_scopes { test_synapse_admin_scopes {
allow with input.user as user allow with input.user as user
with input.client as client
with data.admin_users as ["john"] with data.admin_users as ["john"]
with input.authorization_grant as {"scope": "urn:synapse:admin:*"} with input.grant_type as "authorization_code"
with input.scope as "urn:synapse:admin:*"
not allow with input.user as user not allow with input.user as user
with input.client as client
with data.admin_users as [] with data.admin_users as []
with input.authorization_grant as {"scope": "urn:synapse:admin:*"} with input.grant_type as "authorization_code"
with input.scope as "urn:synapse:admin:*"
} }
test_mas_scopes { test_mas_scopes {
allow with input.user as user allow with input.user as user
with input.authorization_grant as {"scope": "urn:mas:graphql:*"} with input.client as client
with input.scope as "urn:mas:graphql:*"
allow with input.user as user allow with input.user as user
with input.client as client
with data.admin_users as ["john"] with data.admin_users as ["john"]
with input.authorization_grant as {"scope": "urn:mas:admin"} with input.grant_type as "authorization_code"
with input.scope as "urn:mas:admin"
not allow with input.user as user not allow with input.user as user
with input.client as client
with data.admin_users as [] with data.admin_users as []
with input.authorization_grant as {"scope": "urn:mas:admin"} with input.grant_type as "authorization_code"
with input.scope as "urn:mas:admin"
} }

View File

@@ -4,22 +4,34 @@
"description": "Input for the authorization grant policy.", "description": "Input for the authorization grant policy.",
"type": "object", "type": "object",
"required": [ "required": [
"authorization_grant",
"client", "client",
"grant_type",
"scope",
"user" "user"
], ],
"properties": { "properties": {
"authorization_grant": {
"type": "object",
"additionalProperties": true
},
"client": { "client": {
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"grant_type": {
"$ref": "#/definitions/GrantType"
},
"scope": {
"type": "string"
},
"user": { "user": {
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
} }
},
"definitions": {
"GrantType": {
"type": "string",
"enum": [
"authorization_code",
"client_credentials"
]
}
} }
} }