Skip to content

Commit

Permalink
Merge pull request #395 from sebadob/expand-scope-groups-roles-valida…
Browse files Browse the repository at this point in the history
…tion

Adjust validation for `roles`, `groups` and `scopes` names
  • Loading branch information
sebadob committed May 2, 2024
2 parents f6da4c6 + b424732 commit a5982d9
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 57 deletions.
2 changes: 1 addition & 1 deletion frontend/src/components/admin/groups/GroupConfig.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
let formErrors = {};
const schema = yup.object().shape({
name: yup.string().trim().matches(REGEX_ROLES, "Can only contain: 'a-z0-9-_/', length: 2-128"),
name: yup.string().trim().matches(REGEX_ROLES, "Can only contain: 'a-z0-9-_/:', length: 2-64"),
});
function handleKeyPress(event) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/admin/roles/RoleConfig.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
let formErrors = {};
const schema = yup.object().shape({
name: yup.string().trim().matches(REGEX_ROLES, "Can only contain: 'a-z0-9-_/', length: 2-128"),
name: yup.string().trim().matches(REGEX_ROLES, "Can only contain: 'a-z0-9-_/:', length: 2-64"),
});
function handleKeyPress(event) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/admin/scopes/ScopeConfig.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
let formErrors = {};
const schema = yup.object().shape({
name: yup.string().trim().matches(REGEX_ROLES, "Can only contain: 'a-z0-9-_/', length: 2-128"),
name: yup.string().trim().matches(REGEX_ROLES, "Can only contain: 'a-z0-9-_/:', length: 2-64"),
});
function handleKeyPress(event) {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const REGEX_CONTACT = /^[a-zA-Z0-9+.@/:]{0,48}$/gm;
export const REGEX_LOWERCASE_SPACE = /^[a-z0-9-_\/\s]{2,128}$/gm;
export const REGEX_PROVIDER_SCOPE = /^[a-z0-9-_\/:\s]{0,128}$/gm;
export const REGEX_PEM = /^(-----BEGIN CERTIFICATE-----)[a-zA-Z0-9+/=\n]+(-----END CERTIFICATE-----)$/gm;
export const REGEX_ROLES = /^[a-z0-9\-_/]{2,128}$/gm;
export const REGEX_ROLES = /^[a-z0-9\-_/:]{2,64}$/gm;
export const REGEX_URI = /^[a-zA-Z0-9,.:/_\-&?=~#!$'()*+%]*$/gm;
export const REGEX_URI_SPACE = /^[a-zA-Z0-9,.:/_\-&?=~#!$'()*+%\s]+$/m;
export const REGEX_IP_V4 = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/gm;
Expand Down
4 changes: 4 additions & 0 deletions migrations/postgres/24_roles_groups_scopes_limit.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- remove the limit checks on id and name fields for roles, groups, scopes
-- to allow longer scope names like i.e. 'urn:ietf:params:oauth:grant-type:device_code'

-- noop on postgres -> has been done with past migrations already
55 changes: 55 additions & 0 deletions migrations/sqlite/24_roles_groups_scopes_limit.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
-- remove the limit checks on id and name fields for roles, groups, scopes
-- to allow longer scope names like i.e. 'urn:ietf:params:oauth:grant-type:device_code'

create table scopes_dg_tmp
(
id varchar(36) not null
constraint scopes_pk
primary key,
name varchar not null,
attr_include_access varchar,
attr_include_id varchar
);

insert into scopes_dg_tmp(id, name, attr_include_access, attr_include_id)
select id, name, attr_include_access, attr_include_id
from scopes;

drop table scopes;

alter table scopes_dg_tmp
rename to scopes;

create table groups_dg_tmp
(
id varchar not null
constraint groups_pk
primary key,
name varchar not null
);

insert into groups_dg_tmp(id, name)
select id, name
from groups;

drop table groups;

alter table groups_dg_tmp
rename to groups;

create table roles_dg_tmp
(
id varchar not null
constraint roles_pk
primary key,
name varchar not null
);

insert into roles_dg_tmp(id, name)
select id, name
from roles;

drop table roles;

alter table roles_dg_tmp
rename to roles;
8 changes: 5 additions & 3 deletions rauthy-common/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ lazy_static! {

pub static ref RE_ATTR: Regex = Regex::new(r"^[a-zA-Z0-9-_/]{2,32}$").unwrap();
pub static ref RE_ATTR_DESC: Regex = Regex::new(r"^[a-zA-Z0-9-_/\s]{0,128}$").unwrap();
pub static ref RE_SCOPE: Regex = Regex::new(r"^[a-z0-9-_/:\s]{0,128}$").unwrap();
pub static ref RE_ALNUM: Regex = Regex::new(r"^[a-zA-Z0-9]+$").unwrap();
pub static ref RE_ALNUM_24: Regex = Regex::new(r"^[a-zA-Z0-9]{24}$").unwrap();
pub static ref RE_ALNUM_48: Regex = Regex::new(r"^[a-zA-Z0-9]{48}$").unwrap();
Expand All @@ -118,14 +117,17 @@ lazy_static! {
pub static ref RE_DATE_STR: Regex = Regex::new(r"^[0-9]{4}-[0-9]{2}-[0-9]{2}$").unwrap();
pub static ref RE_GRANT_TYPES: Regex = Regex::new(r"^(authorization_code|client_credentials|urn:ietf:params:oauth:grant-type:device_code|password|refresh_token)$").unwrap();
pub static ref RE_GRANT_TYPES_EPHEMERAL: Regex = Regex::new(r"^(authorization_code|client_credentials|password|refresh_token)$").unwrap();
pub static ref RE_GROUPS: Regex = Regex::new(r"^[a-z0-9-_/,:]{2,64}$").unwrap();
pub static ref RE_LOWERCASE: Regex = Regex::new(r"^[a-z0-9-_/]{2,128}$").unwrap();
pub static ref RE_LOWERCASE_SPACE: Regex = Regex::new(r"^[a-z0-9-_/\s]{2,128}$").unwrap();
pub static ref RE_GROUPS: Regex = Regex::new(r"^[a-z0-9-_/,]{2,32}$").unwrap();
pub static ref RE_MFA_CODE: Regex = Regex::new(r"^[a-zA-Z0-9]{48}$").unwrap();
pub static ref RE_PEM: Regex = Regex::new(r"^(-----BEGIN CERTIFICATE-----)[a-zA-Z0-9+/=\n]+(-----END CERTIFICATE-----)$").unwrap();
pub static ref RE_PHONE: Regex = Regex::new(r"^\+[0-9]{0,32}$").unwrap();
pub static ref RE_STREET: Regex = Regex::new(r"^[a-zA-Z0-9À-ÿ-.\s]{0,48}$").unwrap();
// we have a pretty high upper limit for characters here just to be sure that even if
// multiple values like 'urn:ietf:params:oauth:grant-type:device_code' would not fail
pub static ref RE_SCOPE_SPACE: Regex = Regex::new(r"^[a-z0-9-_/:\s]{0,512}$").unwrap();
pub static ref RE_SEARCH: Regex = Regex::new(r"^[a-zA-Z0-9,.:/_\-&?=~#!$'()*+%@]+$").unwrap();
pub static ref RE_STREET: Regex = Regex::new(r"^[a-zA-Z0-9À-ÿ-.\s]{0,48}$").unwrap();
pub static ref RE_URI: Regex = Regex::new(r"^[a-zA-Z0-9,.:/_\-&?=~#!$'()*+%]+$").unwrap();
pub static ref RE_USER_NAME: Regex = Regex::new(r"^[a-zA-Z0-9À-ÿ-\s]{2,32}$").unwrap();
pub static ref RE_TOKEN_68: Regex = Regex::new(r"^[a-zA-Z0-9-._~+/]+=*$").unwrap();
Expand Down
104 changes: 54 additions & 50 deletions rauthy-models/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ use actix_web::http::header;
use actix_web::HttpRequest;
use css_color::Srgb;
use rauthy_common::constants::{
RE_ALNUM, RE_ALNUM_48, RE_ALNUM_64, RE_ALNUM_SPACE, RE_API_KEY, RE_APP_ID, RE_ATTR,
RE_ATTR_DESC, RE_CHALLENGE, RE_CITY, RE_CLIENT_ID_EPHEMERAL, RE_CLIENT_NAME, RE_CODE_CHALLENGE,
RE_CODE_VERIFIER, RE_CONTACT, RE_DATE_STR, RE_GRANT_TYPES, RE_GROUPS, RE_LOWERCASE,
RE_LOWERCASE_SPACE, RE_MFA_CODE, RE_PEM, RE_PHONE, RE_SCOPE, RE_SEARCH, RE_STREET,
RE_TOKEN_ENDPOINT_AUTH_METHOD, RE_URI, RE_USER_NAME,
RE_ALNUM, RE_ALNUM_48, RE_ALNUM_64, RE_API_KEY, RE_APP_ID, RE_ATTR, RE_ATTR_DESC, RE_CHALLENGE,
RE_CITY, RE_CLIENT_ID_EPHEMERAL, RE_CLIENT_NAME, RE_CODE_CHALLENGE, RE_CODE_VERIFIER,
RE_CONTACT, RE_DATE_STR, RE_GRANT_TYPES, RE_GROUPS, RE_LOWERCASE, RE_MFA_CODE, RE_PEM,
RE_PHONE, RE_SCOPE_SPACE, RE_SEARCH, RE_STREET, RE_TOKEN_ENDPOINT_AUTH_METHOD, RE_URI,
RE_USER_NAME,
};
use rauthy_common::error_response::{ErrorResponse, ErrorResponseType};
use rauthy_common::utils::base64_decode;
Expand Down Expand Up @@ -76,8 +76,8 @@ pub struct AuthRequest {
/// Validation: `[a-z0-9-_/]{2,128}`
#[validate(regex(path = "RE_LOWERCASE", code = "[a-z0-9-_/]{2,128}"))]
pub response_type: String,
/// Validation: `[a-zA-Z0-9À-ÿ-s]{2,128}`
#[validate(regex(path = "RE_CLIENT_NAME", code = "[a-zA-Z0-9À-ÿ-s]{2,128}"))]
/// Validation: `[a-z0-9-_/:\s]{0,512}`
#[validate(regex(path = "RE_SCOPE_SPACE", code = "[a-z0-9-_/:\\s]{0,512}"))]
#[serde(default = "default_scope")]
pub scope: String,
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
Expand Down Expand Up @@ -163,8 +163,8 @@ pub struct DeviceGrantRequest {
/// Validation: max length is 256
#[validate(length(max = 256))]
pub client_secret: Option<String>,
/// Validation: `[a-z0-9-_/:\s]{0,128}`
#[validate(regex(path = "RE_SCOPE", code = "[a-z0-9-_/:\\s]{0,128}"))]
/// Validation: `[a-z0-9-_/:\s]{0,512}`
#[validate(regex(path = "RE_SCOPE_SPACE", code = "[a-z0-9-_/:\\s]{0,512}"))]
pub scope: Option<String>,
}

Expand Down Expand Up @@ -225,8 +225,8 @@ pub struct EphemeralClientRequest {
/// Validation: `60 <= access_token_lifetime <= 86400`
#[validate(range(min = 60, max = 86400))]
pub default_max_age: Option<i32>,
/// Validation: `Vec<^[a-z0-9-_/\s]{2,128}$>`
#[validate(regex(path = "RE_LOWERCASE_SPACE", code = "[a-z0-9-_/\\s]{2,128}"))]
/// Validation: `[a-z0-9-_/:\s]{0,512}`
#[validate(regex(path = "RE_SCOPE_SPACE", code = "[a-z0-9-_/:\\s]{0,512}"))]
pub scope: Option<String>,
pub require_auth_time: Option<bool>,

Expand Down Expand Up @@ -296,8 +296,8 @@ pub struct LoginRequest {
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
#[validate(regex(path = "RE_URI", code = "[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$"))]
pub redirect_uri: String,
/// Validation: `Vec<^[a-zA-Z0-9\\s]$>`
#[validate(custom(function = "validate_vec_scope"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_scopes"))]
pub scopes: Option<Vec<String>>,
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
#[validate(regex(path = "RE_URI", code = "[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$"))]
Expand All @@ -324,8 +324,8 @@ pub struct LoginRefreshRequest {
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
#[validate(regex(path = "RE_URI", code = "[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$"))]
pub redirect_uri: String,
/// Validation: `Vec<^[a-zA-Z0-9\\s]$>`
#[validate(custom(function = "validate_vec_scope"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_scopes"))]
pub scopes: Option<Vec<String>>,
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
#[validate(regex(path = "RE_URI", code = "[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$"))]
Expand Down Expand Up @@ -458,8 +458,8 @@ pub struct DynamicClientRequest {

#[derive(Serialize, Deserialize, Validate, ToSchema)]
pub struct NewGroupRequest {
/// Validation: `^[a-z0-9-_/,]{2,32}$`
#[validate(regex(path = "RE_GROUPS", code = "^[a-z0-9-_/,]{2,32}$"))]
/// Validation: `^[a-z0-9-_/,:]{2,64}$`
#[validate(regex(path = "RE_GROUPS", code = "^[a-z0-9-_/,:]{2,64}$"))]
pub group: String,
}

Expand Down Expand Up @@ -545,8 +545,8 @@ pub struct ProviderRequest {
/// Validation: max length is 256
#[validate(length(max = 256))]
pub client_secret: Option<String>,
/// Validation: `[a-z0-9-_/:\s]{0,128}`
#[validate(regex(path = "RE_SCOPE", code = "[a-z0-9-_/:\\s]{0,128}"))]
/// Validation: `[a-z0-9-_/:\s]{0,512}`
#[validate(regex(path = "RE_SCOPE_SPACE", code = "[a-z0-9-_/:\\s]{0,512}"))]
pub scope: String,
/// Validation: `(-----BEGIN CERTIFICATE-----)[a-zA-Z0-9+/=\n]+(-----END CERTIFICATE-----)`
#[validate(regex(
Expand Down Expand Up @@ -600,8 +600,8 @@ pub struct ProviderLoginRequest {
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
#[validate(regex(path = "RE_URI", code = "[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$"))]
pub redirect_uri: String,
/// Validation: `Vec<^[a-zA-Z0-9\\s]$>`
#[validate(custom(function = "validate_vec_scope"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_scopes"))]
pub scopes: Option<Vec<String>>,
/// Validation: `[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$`
#[validate(regex(path = "RE_URI", code = "[a-zA-Z0-9,.:/_-&?=~#!$'()*+%]+$"))]
Expand Down Expand Up @@ -657,11 +657,11 @@ pub struct NewUserRequest {
#[validate(regex(path = "RE_USER_NAME", code = "[a-zA-Z0-9À-ÿ-\\s]{2,32}"))]
pub given_name: String,
pub language: Language,
/// Validation: `Vec<^[a-z0-9-_/,]{2,128}$>`
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_groups"))]
pub groups: Option<Vec<String>>,
/// Validation: `Vec<^[a-z0-9-_/,]{2,128}$>`
#[validate(custom(function = "validate_vec_groups"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_roles"))]
pub roles: Vec<String>,
#[validate(range(min = 1672527600, max = 4070905200))]
pub user_expires: Option<i64>,
Expand All @@ -687,8 +687,8 @@ pub struct NewUserRegistrationRequest {

#[derive(Serialize, Deserialize, Validate, ToSchema)]
pub struct NewRoleRequest {
/// Validation: `^[a-z0-9-_/,]{2,32}$`
#[validate(regex(path = "RE_GROUPS", code = "^[a-z0-9-_/,]{2,32}$"))]
/// Validation: `^[a-z0-9-_/,:]{2,64}$`
#[validate(regex(path = "RE_GROUPS", code = "^[a-z0-9-_/,:]{2,64}$"))]
pub role: String,
}

Expand All @@ -711,8 +711,9 @@ pub struct PasskeyRequest {

#[derive(Debug, Clone, Serialize, Deserialize, Validate, ToSchema)]
pub struct ScopeRequest {
/// Validation: `^[a-z0-9-_/,]{2,32}$`
#[validate(regex(path = "RE_GROUPS", code = "^[a-z0-9-_/,]{2,32}$"))]
// `RE_GROUPS` is correct here
/// Validation: `^[a-z0-9-_/,:]{2,64}$`
#[validate(regex(path = "RE_GROUPS", code = "^[a-z0-9-_/,:]{2,64}$"))]
pub scope: String,
/// Validation: `^[a-zA-Z0-9-_/]{2,128}$`
#[validate(custom(function = "validate_vec_attr"))]
Expand Down Expand Up @@ -861,11 +862,11 @@ pub struct UpdateClientRequest {
/// Validation: `10 <= access_token_lifetime <= 86400`
#[validate(range(min = 10, max = 86400))]
pub access_token_lifetime: i32,
/// Validation: `Vec<^[a-z0-9-_/]{2,128}$>`
#[validate(custom(function = "validate_vec_lowercase"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_scopes"))]
pub scopes: Vec<String>,
/// Validation: `Vec<^[a-z0-9-_/]{2,128}$>`
#[validate(custom(function = "validate_vec_lowercase"))]
/// Validation: `Vec<^[a-z0-9-_/:\s]{0,64}$>`
#[validate(custom(function = "validate_vec_scopes"))]
pub default_scopes: Vec<String>,
/// Validation: `Vec<^(plain|S256)$>`
#[validate(custom(function = "validate_vec_challenge"))]
Expand Down Expand Up @@ -893,11 +894,11 @@ pub struct UpdateUserRequest {
pub language: Option<Language>,
/// Validation: Applies password policy
pub password: Option<String>,
/// Validation: `Vec<^[a-z0-9-_/]{2,128}$>`
#[validate(custom(function = "validate_vec_lowercase"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_roles"))]
pub roles: Vec<String>,
/// Validation: `Vec<^[a-z0-9-_/]{2,128}$>`
#[validate(custom(function = "validate_vec_lowercase"))]
/// Validation: `Vec<^[a-z0-9-_/,:]{2,64}$>`
#[validate(custom(function = "validate_vec_groups"))]
pub groups: Option<Vec<String>>,
pub enabled: bool,
pub email_verified: bool,
Expand Down Expand Up @@ -1103,11 +1104,11 @@ fn validate_vec_grant_types(value: &[String]) -> Result<(), ValidationError> {
Ok(())
}

fn validate_vec_lowercase(value: &[String]) -> Result<(), ValidationError> {
fn validate_vec_uri(value: &[String]) -> Result<(), ValidationError> {
let mut err = None;
value.iter().for_each(|v| {
if !RE_LOWERCASE.is_match(v) {
err = Some("^[a-z0-9-_/]{2,128}$");
if !RE_URI.is_match(v) {
err = Some("^[a-zA-Z0-9,.:/_\\-&?=~#!$'()*+%]+$");
}
});
if let Some(e) = err {
Expand All @@ -1116,11 +1117,11 @@ fn validate_vec_lowercase(value: &[String]) -> Result<(), ValidationError> {
Ok(())
}

fn validate_vec_uri(value: &[String]) -> Result<(), ValidationError> {
fn validate_vec_grant_type(value: &[String]) -> Result<(), ValidationError> {
let mut err = None;
value.iter().for_each(|v| {
if !RE_URI.is_match(v) {
err = Some("^[a-zA-Z0-9,.:/_\\-&?=~#!$'()*+%]+$");
if !RE_GRANT_TYPES.is_match(v) {
err = Some("authorization_code|client_credentials|password|refresh_token");
}
});
if let Some(e) = err {
Expand All @@ -1129,11 +1130,14 @@ fn validate_vec_uri(value: &[String]) -> Result<(), ValidationError> {
Ok(())
}

fn validate_vec_grant_type(value: &[String]) -> Result<(), ValidationError> {
// validate_vec_groups, _roles and _scopes do the same thing but are 3 functions just to
// be clear in the validation fields above that it does not create confusion, even if they
// all use the same `RE_GROUPS` regex.
fn validate_vec_groups(value: &[String]) -> Result<(), ValidationError> {
let mut err = None;
value.iter().for_each(|v| {
if !RE_GRANT_TYPES.is_match(v) {
err = Some("authorization_code|client_credentials|password|refresh_token");
if !RE_GROUPS.is_match(v) {
err = Some("^[a-z0-9-_/,:]{2,64}$");
}
});
if let Some(e) = err {
Expand All @@ -1142,11 +1146,11 @@ fn validate_vec_grant_type(value: &[String]) -> Result<(), ValidationError> {
Ok(())
}

fn validate_vec_groups(value: &[String]) -> Result<(), ValidationError> {
fn validate_vec_roles(value: &[String]) -> Result<(), ValidationError> {
let mut err = None;
value.iter().for_each(|v| {
if !RE_GROUPS.is_match(v) {
err = Some("^[a-z0-9-_/,]{2,128}$");
err = Some("^[a-z0-9-_/,:]{2,64}$");
}
});
if let Some(e) = err {
Expand All @@ -1155,11 +1159,11 @@ fn validate_vec_groups(value: &[String]) -> Result<(), ValidationError> {
Ok(())
}

fn validate_vec_scope(value: &[String]) -> Result<(), ValidationError> {
fn validate_vec_scopes(value: &[String]) -> Result<(), ValidationError> {
let mut err = None;
value.iter().for_each(|v| {
if !RE_ALNUM_SPACE.is_match(v) {
err = Some("^[a-zA-Z0-9\\s]$");
if !RE_GROUPS.is_match(v) {
err = Some("^[a-z0-9-_/,:]{2,64}$");
}
});
if let Some(e) = err {
Expand Down

0 comments on commit a5982d9

Please sign in to comment.