You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-28 11:02:02 +03:00
Properly implement Display and FromStr for oauth2-types enums
Use SerializeDisplay and DeserializeFromStr derives. Add tests for serialize and deserialize implemntations.
This commit is contained in:
committed by
Quentin Gliech
parent
94ba03a273
commit
c4e495a84a
@ -14,8 +14,9 @@
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use parse_display::{Display, FromStr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_enum_str::{Deserialize_enum_str, Serialize_enum_str};
|
||||
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||
|
||||
/// A client error returned by an authorization server.
|
||||
///
|
||||
@ -55,8 +56,8 @@ impl From<ClientErrorCode> for ClientError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize_enum_str, Deserialize_enum_str)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Display, FromStr, SerializeDisplay, DeserializeFromStr)]
|
||||
#[display(style = "snake_case")]
|
||||
pub enum ClientErrorCode {
|
||||
/// `invalid_request`
|
||||
///
|
||||
@ -225,7 +226,7 @@ pub enum ClientErrorCode {
|
||||
InvalidClientMetadata,
|
||||
|
||||
/// Another error code.
|
||||
#[serde(other)]
|
||||
#[display("{0}")]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
@ -307,3 +308,195 @@ impl ClientErrorCode {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn serialize_client_error_code() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidRequest).unwrap(),
|
||||
"\"invalid_request\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidClient).unwrap(),
|
||||
"\"invalid_client\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidGrant).unwrap(),
|
||||
"\"invalid_grant\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::UnauthorizedClient).unwrap(),
|
||||
"\"unauthorized_client\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::UnsupportedGrantType).unwrap(),
|
||||
"\"unsupported_grant_type\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::AccessDenied).unwrap(),
|
||||
"\"access_denied\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::UnsupportedResponseType).unwrap(),
|
||||
"\"unsupported_response_type\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidScope).unwrap(),
|
||||
"\"invalid_scope\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::ServerError).unwrap(),
|
||||
"\"server_error\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::TemporarilyUnavailable).unwrap(),
|
||||
"\"temporarily_unavailable\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InteractionRequired).unwrap(),
|
||||
"\"interaction_required\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::LoginRequired).unwrap(),
|
||||
"\"login_required\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::AccountSelectionRequired).unwrap(),
|
||||
"\"account_selection_required\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::ConsentRequired).unwrap(),
|
||||
"\"consent_required\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidRequestUri).unwrap(),
|
||||
"\"invalid_request_uri\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidRequestObject).unwrap(),
|
||||
"\"invalid_request_object\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::RequestNotSupported).unwrap(),
|
||||
"\"request_not_supported\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::RequestUriNotSupported).unwrap(),
|
||||
"\"request_uri_not_supported\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::RegistrationNotSupported).unwrap(),
|
||||
"\"registration_not_supported\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidRedirectUri).unwrap(),
|
||||
"\"invalid_redirect_uri\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::InvalidClientMetadata).unwrap(),
|
||||
"\"invalid_client_metadata\""
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClientErrorCode::Unknown("unknown_error_code".to_owned()))
|
||||
.unwrap(),
|
||||
"\"unknown_error_code\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_client_error_code() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_request\"").unwrap(),
|
||||
ClientErrorCode::InvalidRequest
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_client\"").unwrap(),
|
||||
ClientErrorCode::InvalidClient
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_grant\"").unwrap(),
|
||||
ClientErrorCode::InvalidGrant
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"unauthorized_client\"").unwrap(),
|
||||
ClientErrorCode::UnauthorizedClient
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"unsupported_grant_type\"").unwrap(),
|
||||
ClientErrorCode::UnsupportedGrantType
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"access_denied\"").unwrap(),
|
||||
ClientErrorCode::AccessDenied
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"unsupported_response_type\"").unwrap(),
|
||||
ClientErrorCode::UnsupportedResponseType
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_scope\"").unwrap(),
|
||||
ClientErrorCode::InvalidScope
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"server_error\"").unwrap(),
|
||||
ClientErrorCode::ServerError
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"temporarily_unavailable\"").unwrap(),
|
||||
ClientErrorCode::TemporarilyUnavailable
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"interaction_required\"").unwrap(),
|
||||
ClientErrorCode::InteractionRequired
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"login_required\"").unwrap(),
|
||||
ClientErrorCode::LoginRequired
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"account_selection_required\"").unwrap(),
|
||||
ClientErrorCode::AccountSelectionRequired
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"consent_required\"").unwrap(),
|
||||
ClientErrorCode::ConsentRequired
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_request_uri\"").unwrap(),
|
||||
ClientErrorCode::InvalidRequestUri
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_request_object\"").unwrap(),
|
||||
ClientErrorCode::InvalidRequestObject
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"request_not_supported\"").unwrap(),
|
||||
ClientErrorCode::RequestNotSupported
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"request_uri_not_supported\"").unwrap(),
|
||||
ClientErrorCode::RequestUriNotSupported
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"registration_not_supported\"").unwrap(),
|
||||
ClientErrorCode::RegistrationNotSupported
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_redirect_uri\"").unwrap(),
|
||||
ClientErrorCode::InvalidRedirectUri
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"invalid_client_metadata\"").unwrap(),
|
||||
ClientErrorCode::InvalidClientMetadata
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClientErrorCode>("\"unknown_error_code\"").unwrap(),
|
||||
ClientErrorCode::Unknown("unknown_error_code".to_owned())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,29 +22,36 @@ use mas_iana::{
|
||||
PkceCodeChallengeMethod,
|
||||
},
|
||||
};
|
||||
use parse_display::{Display, FromStr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use serde_with::{skip_serializing_none, DeserializeFromStr, SerializeDisplay};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use crate::requests::{Display, GrantType, Prompt, ResponseMode};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(
|
||||
SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr,
|
||||
)]
|
||||
#[display(style = "lowercase")]
|
||||
pub enum ApplicationType {
|
||||
Web,
|
||||
Native,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(
|
||||
SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr,
|
||||
)]
|
||||
#[display(style = "lowercase")]
|
||||
pub enum SubjectType {
|
||||
Public,
|
||||
Pairwise,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[derive(
|
||||
SerializeDisplay, DeserializeFromStr, Clone, Copy, PartialEq, Eq, Hash, Debug, Display, FromStr,
|
||||
)]
|
||||
#[display(style = "lowercase")]
|
||||
pub enum ClaimType {
|
||||
Normal,
|
||||
Aggregated,
|
||||
@ -918,7 +925,7 @@ mod tests {
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
use super::{ProviderMetadata, ProviderMetadataVerificationError, SubjectType};
|
||||
use super::*;
|
||||
|
||||
fn valid_provider_metadata() -> (ProviderMetadata, Url) {
|
||||
let issuer = Url::parse("https://localhost").unwrap();
|
||||
@ -1407,4 +1414,84 @@ mod tests {
|
||||
Some(Url::parse("https://localhost/par?query#fragment").unwrap());
|
||||
metadata.validate(&issuer).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_application_type() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ApplicationType::Web).unwrap(),
|
||||
"\"web\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ApplicationType::Native).unwrap(),
|
||||
"\"native\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_application_type() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ApplicationType>("\"web\"").unwrap(),
|
||||
ApplicationType::Web
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ApplicationType>("\"native\"").unwrap(),
|
||||
ApplicationType::Native
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_subject_type() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&SubjectType::Public).unwrap(),
|
||||
"\"public\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&SubjectType::Pairwise).unwrap(),
|
||||
"\"pairwise\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_subject_type() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<SubjectType>("\"public\"").unwrap(),
|
||||
SubjectType::Public
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<SubjectType>("\"pairwise\"").unwrap(),
|
||||
SubjectType::Pairwise
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_claim_type() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClaimType::Normal).unwrap(),
|
||||
"\"normal\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClaimType::Aggregated).unwrap(),
|
||||
"\"aggregated\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ClaimType::Distributed).unwrap(),
|
||||
"\"distributed\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_claim_type() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClaimType>("\"normal\"").unwrap(),
|
||||
ClaimType::Normal
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClaimType>("\"aggregated\"").unwrap(),
|
||||
ClaimType::Aggregated
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ClaimType>("\"distributed\"").unwrap(),
|
||||
ClaimType::Distributed
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ use mas_iana::oauth::{
|
||||
use parse_display::{Display, FromStr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{
|
||||
formats::SpaceSeparator, serde_as, skip_serializing_none, DisplayFromStr, DurationSeconds,
|
||||
StringWithSeparator, TimestampSeconds,
|
||||
formats::SpaceSeparator, serde_as, skip_serializing_none, DeserializeFromStr, DisplayFromStr,
|
||||
DurationSeconds, SerializeDisplay, StringWithSeparator, TimestampSeconds,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
@ -46,10 +46,10 @@ use crate::scope::Scope;
|
||||
Copy,
|
||||
Display,
|
||||
FromStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
SerializeDisplay,
|
||||
DeserializeFromStr,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[display(style = "snake_case")]
|
||||
pub enum ResponseMode {
|
||||
/// Authorization Response parameters are encoded in the query string added
|
||||
/// to the `redirect_uri`.
|
||||
@ -84,10 +84,10 @@ pub enum ResponseMode {
|
||||
Copy,
|
||||
Display,
|
||||
FromStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
SerializeDisplay,
|
||||
DeserializeFromStr,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[display(style = "snake_case")]
|
||||
pub enum Display {
|
||||
/// The Authorization Server should display the authentication and consent
|
||||
/// UI consistent with a full User Agent page view.
|
||||
@ -129,11 +129,10 @@ impl Default for Display {
|
||||
Copy,
|
||||
Display,
|
||||
FromStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
SerializeDisplay,
|
||||
DeserializeFromStr,
|
||||
)]
|
||||
#[display(style = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Prompt {
|
||||
/// The Authorization Server must not display any authentication or consent
|
||||
/// user interface pages.
|
||||
@ -366,10 +365,10 @@ pub struct ClientCredentialsGrant {
|
||||
Copy,
|
||||
Display,
|
||||
FromStr,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
SerializeDisplay,
|
||||
DeserializeFromStr,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[display(style = "snake_case")]
|
||||
pub enum GrantType {
|
||||
/// [`authorization_code`](https://www.rfc-editor.org/rfc/rfc6749#section-4.1)
|
||||
AuthorizationCode,
|
||||
@ -387,11 +386,11 @@ pub enum GrantType {
|
||||
Password,
|
||||
|
||||
/// [`urn:ietf:params:oauth:grant-type:device_code`](https://www.rfc-editor.org/rfc/rfc8628)
|
||||
#[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
|
||||
#[display("urn:ietf:params:oauth:grant-type:device_code")]
|
||||
DeviceCode,
|
||||
|
||||
/// [`urn:openid:params:grant-type:ciba`](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)
|
||||
#[serde(rename = "urn:openid:params:grant-type:ciba")]
|
||||
#[display("urn:openid:params:grant-type:ciba")]
|
||||
ClientInitiatedBackchannelAuthentication,
|
||||
}
|
||||
|
||||
@ -667,4 +666,106 @@ mod tests {
|
||||
GrantType::ClientInitiatedBackchannelAuthentication
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_response_mode() {
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ResponseMode::Query).unwrap(),
|
||||
"\"query\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ResponseMode::Fragment).unwrap(),
|
||||
"\"fragment\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&ResponseMode::FormPost).unwrap(),
|
||||
"\"form_post\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_response_mode() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ResponseMode>("\"query\"").unwrap(),
|
||||
ResponseMode::Query
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ResponseMode>("\"fragment\"").unwrap(),
|
||||
ResponseMode::Fragment
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<ResponseMode>("\"form_post\"").unwrap(),
|
||||
ResponseMode::FormPost
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_display() {
|
||||
assert_eq!(serde_json::to_string(&Display::Page).unwrap(), "\"page\"");
|
||||
assert_eq!(serde_json::to_string(&Display::Popup).unwrap(), "\"popup\"");
|
||||
assert_eq!(serde_json::to_string(&Display::Touch).unwrap(), "\"touch\"");
|
||||
assert_eq!(serde_json::to_string(&Display::Wap).unwrap(), "\"wap\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_display() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Display>("\"page\"").unwrap(),
|
||||
Display::Page
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Display>("\"popup\"").unwrap(),
|
||||
Display::Popup
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Display>("\"touch\"").unwrap(),
|
||||
Display::Touch
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Display>("\"wap\"").unwrap(),
|
||||
Display::Wap
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialize_prompt() {
|
||||
assert_eq!(serde_json::to_string(&Prompt::None).unwrap(), "\"none\"");
|
||||
assert_eq!(serde_json::to_string(&Prompt::Login).unwrap(), "\"login\"");
|
||||
assert_eq!(
|
||||
serde_json::to_string(&Prompt::Consent).unwrap(),
|
||||
"\"consent\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&Prompt::SelectAccount).unwrap(),
|
||||
"\"select_account\""
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::to_string(&Prompt::Create).unwrap(),
|
||||
"\"create\""
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deserialize_prompt() {
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Prompt>("\"none\"").unwrap(),
|
||||
Prompt::None
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Prompt>("\"login\"").unwrap(),
|
||||
Prompt::Login
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Prompt>("\"consent\"").unwrap(),
|
||||
Prompt::Consent
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Prompt>("\"select_account\"").unwrap(),
|
||||
Prompt::SelectAccount
|
||||
);
|
||||
assert_eq!(
|
||||
serde_json::from_str::<Prompt>("\"create\"").unwrap(),
|
||||
Prompt::Create
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user