use async_trait::async_trait;
use opcua_crypto::{SecurityPolicy, Thumbprint};
use opcua_types::{
ByteString, Error, MessageSecurityMode, NodeId, StatusCode, UAString, UserTokenPolicy,
UserTokenType,
};
use tracing::{debug, error};
use crate::identity_token::{
POLICY_ID_ANONYMOUS, POLICY_ID_ISSUED_TOKEN_NONE, POLICY_ID_ISSUED_TOKEN_RSA_15,
POLICY_ID_ISSUED_TOKEN_RSA_OAEP, POLICY_ID_ISSUED_TOKEN_RSA_OAEP_SHA256,
POLICY_ID_USER_PASS_NONE, POLICY_ID_USER_PASS_RSA_15, POLICY_ID_USER_PASS_RSA_OAEP,
POLICY_ID_USER_PASS_RSA_OAEP_SHA256, POLICY_ID_X509,
};
use super::{
address_space::AccessLevel, config::ANONYMOUS_USER_TOKEN_ID, ServerEndpoint, ServerUserToken,
};
use std::{collections::BTreeMap, fmt::Debug};
#[derive(Clone, PartialEq, Eq)]
pub struct Password(String);
impl Debug for Password {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Password").field(&"****").finish()
}
}
impl Password {
pub fn new(password: String) -> Self {
Self(password)
}
pub fn get(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct UserToken(pub String);
#[derive(Debug, Clone)]
pub struct UserSecurityKey {
pub token: UserToken,
pub security_mode: MessageSecurityMode,
pub application_uri: String,
}
impl UserToken {
pub fn is_anonymous(&self) -> bool {
self.0 == ANONYMOUS_USER_TOKEN_ID
}
}
#[derive(Default, Debug, Clone)]
pub struct CoreServerPermissions {
pub read_diagnostics: bool,
}
#[allow(unused)]
#[async_trait]
pub trait AuthManager: Send + Sync + 'static {
async fn authenticate_anonymous_token(&self, endpoint: &ServerEndpoint) -> Result<(), Error> {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Anonymous identity token unsupported",
))
}
async fn authenticate_username_identity_token(
&self,
endpoint: &ServerEndpoint,
username: &str,
password: &Password,
) -> Result<UserToken, Error> {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Username identity token unsupported",
))
}
async fn authenticate_x509_identity_token(
&self,
endpoint: &ServerEndpoint,
signing_thumbprint: &Thumbprint,
) -> Result<UserToken, Error> {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"X509 identity token unsupported",
))
}
async fn authenticate_issued_identity_token(
&self,
endpoint: &ServerEndpoint,
token: &ByteString,
) -> Result<UserToken, Error> {
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Issued identity token unsupported",
))
}
fn effective_user_access_level(
&self,
token: &UserToken,
user_access_level: AccessLevel,
node_id: &NodeId,
) -> AccessLevel {
user_access_level
}
fn is_user_executable(&self, token: &UserToken, method_id: &NodeId) -> bool {
true
}
fn user_token_policies(&self, endpoint: &ServerEndpoint) -> Vec<UserTokenPolicy>;
fn supports_anonymous(&self, endpoint: &ServerEndpoint) -> bool {
self.user_token_policies(endpoint)
.iter()
.any(|e| e.token_type == UserTokenType::Anonymous)
}
fn supports_user_pass(&self, endpoint: &ServerEndpoint) -> bool {
self.user_token_policies(endpoint)
.iter()
.any(|e| e.token_type == UserTokenType::UserName)
}
fn supports_x509(&self, endpoint: &ServerEndpoint) -> bool {
self.user_token_policies(endpoint)
.iter()
.any(|e| e.token_type == UserTokenType::Certificate)
}
fn supports_issued_token(&self, endpoint: &ServerEndpoint) -> bool {
self.user_token_policies(endpoint)
.iter()
.any(|e| e.token_type == UserTokenType::IssuedToken)
}
fn core_permissions(&self, token: &UserToken) -> CoreServerPermissions {
CoreServerPermissions::default()
}
}
pub struct DefaultAuthenticator {
users: BTreeMap<String, ServerUserToken>,
}
impl DefaultAuthenticator {
pub fn new(users: BTreeMap<String, ServerUserToken>) -> Self {
Self { users }
}
}
#[async_trait]
impl AuthManager for DefaultAuthenticator {
async fn authenticate_anonymous_token(&self, endpoint: &ServerEndpoint) -> Result<(), Error> {
if !endpoint.user_token_ids.contains(ANONYMOUS_USER_TOKEN_ID) {
return Err(Error::new(
StatusCode::BadIdentityTokenRejected,
format!(
"Endpoint \"{}\" does not support anonymous authentication",
endpoint.path
),
));
}
Ok(())
}
async fn authenticate_username_identity_token(
&self,
endpoint: &ServerEndpoint,
username: &str,
password: &Password,
) -> Result<UserToken, Error> {
let token_password = password.get();
for user_token_id in &endpoint.user_token_ids {
if let Some(server_user_token) = self.users.get(user_token_id) {
if server_user_token.is_user_pass() && server_user_token.user == username {
let valid = if let Some(server_password) = server_user_token.pass.as_ref() {
server_password.as_bytes() == token_password.as_bytes()
} else {
token_password.is_empty()
};
if !valid {
error!(
"Cannot authenticate \"{}\", password is invalid",
server_user_token.user
);
return Err(Error::new(
StatusCode::BadIdentityTokenRejected,
format!("Cannot authenticate user \"{username}\""),
));
} else {
return Ok(UserToken(user_token_id.clone()));
}
}
}
}
error!(
"Cannot authenticate \"{}\", user not found for endpoint",
username
);
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
format!("Cannot authenticate \"{username}\""),
))
}
async fn authenticate_x509_identity_token(
&self,
endpoint: &ServerEndpoint,
signing_thumbprint: &Thumbprint,
) -> Result<UserToken, Error> {
for user_token_id in &endpoint.user_token_ids {
if let Some(server_user_token) = self.users.get(user_token_id) {
if let Some(ref user_thumbprint) = server_user_token.thumbprint {
if user_thumbprint == signing_thumbprint {
return Ok(UserToken(user_token_id.clone()));
}
}
}
}
Err(Error::new(
StatusCode::BadIdentityTokenRejected,
"Authentication failed",
))
}
fn user_token_policies(&self, endpoint: &ServerEndpoint) -> Vec<UserTokenPolicy> {
let mut user_identity_tokens = Vec::with_capacity(3);
if endpoint.user_token_ids.contains(ANONYMOUS_USER_TOKEN_ID) {
user_identity_tokens.push(UserTokenPolicy {
policy_id: UAString::from(POLICY_ID_ANONYMOUS),
token_type: UserTokenType::Anonymous,
issued_token_type: UAString::null(),
issuer_endpoint_url: UAString::null(),
security_policy_uri: UAString::null(),
});
}
if endpoint.user_token_ids.iter().any(|id| {
id != ANONYMOUS_USER_TOKEN_ID
&& self.users.get(id).is_some_and(|token| token.is_user_pass())
}) {
user_identity_tokens.push(UserTokenPolicy {
policy_id: user_pass_security_policy_id(endpoint),
token_type: UserTokenType::UserName,
issued_token_type: UAString::null(),
issuer_endpoint_url: UAString::null(),
security_policy_uri: user_pass_security_policy_uri(endpoint),
});
}
if endpoint.user_token_ids.iter().any(|id| {
id != ANONYMOUS_USER_TOKEN_ID && self.users.get(id).is_some_and(|token| token.is_x509())
}) {
user_identity_tokens.push(UserTokenPolicy {
policy_id: UAString::from(POLICY_ID_X509),
token_type: UserTokenType::Certificate,
issued_token_type: UAString::null(),
issuer_endpoint_url: UAString::null(),
security_policy_uri: UAString::from(SecurityPolicy::Basic128Rsa15.to_uri()),
});
}
if user_identity_tokens.is_empty() {
debug!(
"user_identity_tokens() returned zero endpoints for endpoint {} / {} {}",
endpoint.path, endpoint.security_policy, endpoint.security_mode
);
}
user_identity_tokens
}
fn core_permissions(&self, token: &UserToken) -> CoreServerPermissions {
self.users
.get(token.0.as_str())
.map(|r| CoreServerPermissions {
read_diagnostics: r.read_diagnostics,
})
.unwrap_or_default()
}
}
pub fn user_pass_security_policy_id(endpoint: &ServerEndpoint) -> UAString {
match endpoint.password_security_policy() {
SecurityPolicy::None => POLICY_ID_USER_PASS_NONE,
SecurityPolicy::Basic128Rsa15 => POLICY_ID_USER_PASS_RSA_15,
SecurityPolicy::Basic256
| SecurityPolicy::Basic256Sha256
| SecurityPolicy::Aes128Sha256RsaOaep => POLICY_ID_USER_PASS_RSA_OAEP,
SecurityPolicy::Aes256Sha256RsaPss => POLICY_ID_USER_PASS_RSA_OAEP_SHA256,
_ => {
panic!("Invalid security policy for username and password")
}
}
.into()
}
pub fn issued_token_security_policy(endpoint: &ServerEndpoint) -> UAString {
match endpoint.password_security_policy() {
SecurityPolicy::None => POLICY_ID_ISSUED_TOKEN_NONE,
SecurityPolicy::Basic128Rsa15 => POLICY_ID_ISSUED_TOKEN_RSA_15,
SecurityPolicy::Basic256
| SecurityPolicy::Basic256Sha256
| SecurityPolicy::Aes128Sha256RsaOaep => POLICY_ID_ISSUED_TOKEN_RSA_OAEP,
SecurityPolicy::Aes256Sha256RsaPss => POLICY_ID_ISSUED_TOKEN_RSA_OAEP_SHA256,
_ => {
panic!("Invalid security policy for username and password")
}
}
.into()
}
pub fn user_pass_security_policy_uri(_endpoint: &ServerEndpoint) -> UAString {
UAString::null()
}