mirror of
https://github.com/lldap/lldap.git
synced 2024-11-25 09:06:03 +00:00
server,graphql: Add a GraphQL method to get the schema
This commit is contained in:
parent
9e1b58d033
commit
31a8ba24a0
8 changed files with 570 additions and 151 deletions
19
schema.graphql
generated
19
schema.graphql
generated
|
@ -39,6 +39,11 @@ input RequestFilter {
|
|||
"DateTime"
|
||||
scalar DateTimeUtc
|
||||
|
||||
type Schema {
|
||||
userSchema: AttributeList!
|
||||
groupSchema: AttributeList!
|
||||
}
|
||||
|
||||
"The fields that can be updated for a group."
|
||||
input UpdateGroupInput {
|
||||
id: Int!
|
||||
|
@ -51,6 +56,7 @@ type Query {
|
|||
users(filters: RequestFilter): [User!]!
|
||||
groups: [Group!]!
|
||||
group(groupId: Int!): Group!
|
||||
schema: Schema!
|
||||
}
|
||||
|
||||
"The details required to create a user."
|
||||
|
@ -76,6 +82,19 @@ type User {
|
|||
groups: [Group!]!
|
||||
}
|
||||
|
||||
type AttributeList {
|
||||
attributes: [AttributeSchema!]!
|
||||
}
|
||||
|
||||
type AttributeSchema {
|
||||
name: String!
|
||||
attributeType: String!
|
||||
isList: Boolean!
|
||||
isVisible: Boolean!
|
||||
isEditable: Boolean!
|
||||
isHardcoded: Boolean!
|
||||
}
|
||||
|
||||
type Success {
|
||||
ok: Boolean!
|
||||
}
|
||||
|
|
|
@ -209,49 +209,6 @@ pub trait BackendHandler:
|
|||
{
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mockall::mock! {
|
||||
pub TestBackendHandler{}
|
||||
impl Clone for TestBackendHandler {
|
||||
fn clone(&self) -> Self;
|
||||
}
|
||||
#[async_trait]
|
||||
impl GroupListerBackendHandler for TestBackendHandler {
|
||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl GroupBackendHandler for TestBackendHandler {
|
||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
|
||||
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
|
||||
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
||||
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl UserListerBackendHandler for TestBackendHandler {
|
||||
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl UserBackendHandler for TestBackendHandler {
|
||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||
async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
|
||||
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
|
||||
async fn delete_user(&self, user_id: &UserId) -> Result<()>;
|
||||
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
|
||||
async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
||||
async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl SchemaBackendHandler for TestBackendHandler {
|
||||
async fn get_schema(&self) -> Result<Schema>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl BackendHandler for TestBackendHandler {}
|
||||
#[async_trait]
|
||||
impl LoginHandler for TestBackendHandler {
|
||||
async fn bind(&self, request: BindRequest) -> Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use base64::Engine;
|
||||
|
|
|
@ -6,9 +6,10 @@ use tracing::info;
|
|||
use crate::domain::{
|
||||
error::Result,
|
||||
handler::{
|
||||
BackendHandler, CreateUserRequest, GroupBackendHandler, GroupListerBackendHandler,
|
||||
GroupRequestFilter, Schema, SchemaBackendHandler, UpdateGroupRequest, UpdateUserRequest,
|
||||
UserBackendHandler, UserListerBackendHandler, UserRequestFilter,
|
||||
AttributeSchema, BackendHandler, CreateUserRequest, GroupBackendHandler,
|
||||
GroupListerBackendHandler, GroupRequestFilter, Schema, SchemaBackendHandler,
|
||||
UpdateGroupRequest, UpdateUserRequest, UserBackendHandler, UserListerBackendHandler,
|
||||
UserRequestFilter,
|
||||
},
|
||||
types::{Group, GroupDetails, GroupId, User, UserAndGroups, UserId},
|
||||
};
|
||||
|
@ -73,7 +74,6 @@ impl ValidationResults {
|
|||
pub trait UserReadableBackendHandler {
|
||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
|
||||
async fn get_schema(&self) -> Result<Schema>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -113,9 +113,6 @@ impl<Handler: BackendHandler> UserReadableBackendHandler for Handler {
|
|||
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>> {
|
||||
<Handler as UserBackendHandler>::get_user_groups(self, user_id).await
|
||||
}
|
||||
async fn get_schema(&self) -> Result<Schema> {
|
||||
<Handler as SchemaBackendHandler>::get_schema(self).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -272,7 +269,15 @@ impl<'a, Handler: SchemaBackendHandler + Sync> SchemaBackendHandler
|
|||
for UserRestrictedListerBackendHandler<'a, Handler>
|
||||
{
|
||||
async fn get_schema(&self) -> Result<Schema> {
|
||||
self.handler.get_schema().await
|
||||
let mut schema = self.handler.get_schema().await?;
|
||||
if self.user_filter.is_some() {
|
||||
let filter_attributes = |attributes: &mut Vec<AttributeSchema>| {
|
||||
attributes.retain(|a| a.is_visible);
|
||||
};
|
||||
filter_attributes(&mut schema.user_attributes.attributes);
|
||||
filter_attributes(&mut schema.group_attributes.attributes);
|
||||
}
|
||||
Ok(schema)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use crate::{
|
||||
domain::{
|
||||
handler::BackendHandler,
|
||||
handler::{BackendHandler, SchemaBackendHandler},
|
||||
ldap::utils::{map_user_field, UserFieldType},
|
||||
types::{GroupDetails, GroupId, JpegPhoto, UserColumn, UserId},
|
||||
},
|
||||
infra::{
|
||||
access_control::{ReadonlyBackendHandler, UserReadableBackendHandler},
|
||||
graphql::api::field_error_callback,
|
||||
schema::PublicSchema,
|
||||
},
|
||||
};
|
||||
use chrono::TimeZone;
|
||||
|
@ -18,6 +19,9 @@ type DomainRequestFilter = crate::domain::handler::UserRequestFilter;
|
|||
type DomainUser = crate::domain::types::User;
|
||||
type DomainGroup = crate::domain::types::Group;
|
||||
type DomainUserAndGroups = crate::domain::types::UserAndGroups;
|
||||
type DomainSchema = crate::infra::schema::PublicSchema;
|
||||
type DomainAttributeList = crate::domain::handler::AttributeList;
|
||||
type DomainAttributeSchema = crate::domain::handler::AttributeSchema;
|
||||
use super::api::Context;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
||||
|
@ -202,6 +206,19 @@ impl<Handler: BackendHandler> Query<Handler> {
|
|||
.await
|
||||
.map(Into::into)?)
|
||||
}
|
||||
|
||||
async fn schema(context: &Context<Handler>) -> FieldResult<Schema<Handler>> {
|
||||
let span = debug_span!("[GraphQL query] get_schema");
|
||||
let handler = context
|
||||
.handler
|
||||
.get_user_restricted_lister_handler(&context.validation_result);
|
||||
Ok(handler
|
||||
.get_schema()
|
||||
.instrument(span)
|
||||
.await
|
||||
.map(Into::<PublicSchema>::into)
|
||||
.map(Into::into)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
|
@ -378,11 +395,105 @@ impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub struct AttributeSchema<Handler: BackendHandler> {
|
||||
schema: DomainAttributeSchema,
|
||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||
}
|
||||
|
||||
#[graphql_object(context = Context<Handler>)]
|
||||
impl<Handler: BackendHandler> AttributeSchema<Handler> {
|
||||
fn name(&self) -> String {
|
||||
self.schema.name.clone()
|
||||
}
|
||||
fn attribute_type(&self) -> String {
|
||||
let name: &'static str = self.schema.attribute_type.into();
|
||||
name.to_owned()
|
||||
}
|
||||
fn is_list(&self) -> bool {
|
||||
self.schema.is_list
|
||||
}
|
||||
fn is_visible(&self) -> bool {
|
||||
self.schema.is_visible
|
||||
}
|
||||
fn is_editable(&self) -> bool {
|
||||
self.schema.is_editable
|
||||
}
|
||||
fn is_hardcoded(&self) -> bool {
|
||||
self.schema.is_hardcoded
|
||||
}
|
||||
}
|
||||
|
||||
impl<Handler: BackendHandler> From<DomainAttributeSchema> for AttributeSchema<Handler> {
|
||||
fn from(value: DomainAttributeSchema) -> Self {
|
||||
Self {
|
||||
schema: value,
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub struct AttributeList<Handler: BackendHandler> {
|
||||
schema: DomainAttributeList,
|
||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||
}
|
||||
|
||||
#[graphql_object(context = Context<Handler>)]
|
||||
impl<Handler: BackendHandler> AttributeList<Handler> {
|
||||
fn attributes(&self) -> Vec<AttributeSchema<Handler>> {
|
||||
self.schema
|
||||
.attributes
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Handler: BackendHandler> From<DomainAttributeList> for AttributeList<Handler> {
|
||||
fn from(value: DomainAttributeList) -> Self {
|
||||
Self {
|
||||
schema: value,
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub struct Schema<Handler: BackendHandler> {
|
||||
schema: DomainSchema,
|
||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||
}
|
||||
|
||||
#[graphql_object(context = Context<Handler>)]
|
||||
impl<Handler: BackendHandler> Schema<Handler> {
|
||||
fn user_schema(&self) -> AttributeList<Handler> {
|
||||
self.schema.get_schema().user_attributes.clone().into()
|
||||
}
|
||||
fn group_schema(&self) -> AttributeList<Handler> {
|
||||
self.schema.get_schema().group_attributes.clone().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<Handler: BackendHandler> From<DomainSchema> for Schema<Handler> {
|
||||
fn from(value: DomainSchema) -> Self {
|
||||
Self {
|
||||
schema: value,
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
domain::handler::MockTestBackendHandler, infra::access_control::ValidationResults,
|
||||
domain::{handler::AttributeList, types::AttributeType},
|
||||
infra::{
|
||||
access_control::{Permission, ValidationResults},
|
||||
test_utils::{setup_default_schema, MockTestBackendHandler},
|
||||
},
|
||||
};
|
||||
use chrono::TimeZone;
|
||||
use juniper::{
|
||||
|
@ -552,4 +663,219 @@ mod tests {
|
|||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn get_schema() {
|
||||
const QUERY: &str = r#"{
|
||||
schema {
|
||||
userSchema {
|
||||
attributes {
|
||||
name
|
||||
attributeType
|
||||
isList
|
||||
isVisible
|
||||
isEditable
|
||||
isHardcoded
|
||||
}
|
||||
}
|
||||
groupSchema {
|
||||
attributes {
|
||||
name
|
||||
attributeType
|
||||
isList
|
||||
isVisible
|
||||
isEditable
|
||||
isHardcoded
|
||||
}
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
|
||||
setup_default_schema(&mut mock);
|
||||
|
||||
let context =
|
||||
Context::<MockTestBackendHandler>::new_for_tests(mock, ValidationResults::admin());
|
||||
|
||||
let schema = schema(Query::<MockTestBackendHandler>::new());
|
||||
assert_eq!(
|
||||
execute(QUERY, None, &schema, &Variables::new(), &context).await,
|
||||
Ok((
|
||||
graphql_value!(
|
||||
{
|
||||
"schema": {
|
||||
"userSchema": {
|
||||
"attributes": [
|
||||
{
|
||||
"name": "avatar",
|
||||
"attributeType": "JpegPhoto",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": true,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "creation_date",
|
||||
"attributeType": "DateTime",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": false,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "display_name",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": true,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "first_name",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": true,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "last_name",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": true,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "mail",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": true,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "user_id",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": false,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "uuid",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": false,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
]
|
||||
},
|
||||
"groupSchema": {
|
||||
"attributes": [
|
||||
{
|
||||
"name": "creation_date",
|
||||
"attributeType": "DateTime",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": false,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "display_name",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": true,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"attributeType": "Integer",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": false,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
{
|
||||
"name": "uuid",
|
||||
"attributeType": "String",
|
||||
"isList": false,
|
||||
"isVisible": true,
|
||||
"isEditable": false,
|
||||
"isHardcoded": true,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn regular_user_doesnt_see_non_visible_attributes() {
|
||||
const QUERY: &str = r#"{
|
||||
schema {
|
||||
userSchema {
|
||||
attributes {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
|
||||
mock.expect_get_schema().times(1).return_once(|| {
|
||||
Ok(crate::domain::handler::Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: vec![crate::domain::handler::AttributeSchema {
|
||||
name: "invisible".to_owned(),
|
||||
attribute_type: AttributeType::JpegPhoto,
|
||||
is_list: false,
|
||||
is_visible: false,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
}],
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
let context = Context::<MockTestBackendHandler>::new_for_tests(
|
||||
mock,
|
||||
ValidationResults {
|
||||
user: UserId::new("bob"),
|
||||
permission: Permission::Regular,
|
||||
},
|
||||
);
|
||||
|
||||
let schema = schema(Query::<MockTestBackendHandler>::new());
|
||||
assert_eq!(
|
||||
execute(QUERY, None, &schema, &Variables::new(), &context).await,
|
||||
Ok((
|
||||
graphql_value!(
|
||||
{
|
||||
"schema": {
|
||||
"userSchema": {
|
||||
"attributes": [
|
||||
{"name": "creation_date"},
|
||||
{"name": "display_name"},
|
||||
{"name": "mail"},
|
||||
{"name": "user_id"},
|
||||
{"name": "uuid"},
|
||||
]
|
||||
}
|
||||
}
|
||||
} ),
|
||||
vec![]
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -671,74 +671,16 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
domain::{error::Result, handler::*, opaque_handler::*, types::*},
|
||||
domain::{handler::*, types::*},
|
||||
infra::test_utils::{setup_default_schema, MockTestBackendHandler},
|
||||
uuid,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use chrono::TimeZone;
|
||||
use ldap3_proto::proto::{LdapDerefAliases, LdapSearchScope, LdapSubstringFilter};
|
||||
use mockall::predicate::eq;
|
||||
use std::collections::HashSet;
|
||||
use tokio;
|
||||
|
||||
mockall::mock! {
|
||||
pub TestBackendHandler{}
|
||||
impl Clone for TestBackendHandler {
|
||||
fn clone(&self) -> Self;
|
||||
}
|
||||
#[async_trait]
|
||||
impl LoginHandler for TestBackendHandler {
|
||||
async fn bind(&self, request: BindRequest) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl GroupListerBackendHandler for TestBackendHandler {
|
||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl GroupBackendHandler for TestBackendHandler {
|
||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
|
||||
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
|
||||
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
||||
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl UserListerBackendHandler for TestBackendHandler {
|
||||
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl UserBackendHandler for TestBackendHandler {
|
||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||
async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
|
||||
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
|
||||
async fn delete_user(&self, user_id: &UserId) -> Result<()>;
|
||||
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
|
||||
async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
||||
async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl SchemaBackendHandler for TestBackendHandler {
|
||||
async fn get_schema(&self) -> Result<Schema>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl BackendHandler for TestBackendHandler {}
|
||||
#[async_trait]
|
||||
impl OpaqueHandler for TestBackendHandler {
|
||||
async fn login_start(
|
||||
&self,
|
||||
request: login::ClientLoginStartRequest
|
||||
) -> Result<login::ServerLoginStartResponse>;
|
||||
async fn login_finish(&self, request: login::ClientLoginFinishRequest) -> Result<UserId>;
|
||||
async fn registration_start(
|
||||
&self,
|
||||
request: registration::ClientRegistrationStartRequest
|
||||
) -> Result<registration::ServerRegistrationStartResponse>;
|
||||
async fn registration_finish(
|
||||
&self,
|
||||
request: registration::ClientRegistrationFinishRequest
|
||||
) -> Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
fn make_user_search_request<S: Into<String>>(
|
||||
filter: LdapFilter,
|
||||
attrs: Vec<S>,
|
||||
|
@ -807,44 +749,6 @@ mod tests {
|
|||
setup_bound_handler_with_group(mock, "lldap_admin").await
|
||||
}
|
||||
|
||||
fn setup_default_schema(mock: &mut MockTestBackendHandler) {
|
||||
mock.expect_get_schema().returning(|| {
|
||||
Ok(Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: vec![
|
||||
AttributeSchema {
|
||||
name: "avatar".to_owned(),
|
||||
attribute_type: AttributeType::JpegPhoto,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "first_name".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "last_name".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_bind() {
|
||||
let mut mock = MockTestBackendHandler::new();
|
||||
|
|
|
@ -10,6 +10,10 @@ pub mod ldap_handler;
|
|||
pub mod ldap_server;
|
||||
pub mod logging;
|
||||
pub mod mail;
|
||||
pub mod schema;
|
||||
pub mod sql_backend_handler;
|
||||
pub mod tcp_backend_handler;
|
||||
pub mod tcp_server;
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test_utils;
|
||||
|
|
104
server/src/infra/schema.rs
Normal file
104
server/src/infra/schema.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use crate::domain::{
|
||||
handler::{AttributeSchema, Schema},
|
||||
types::AttributeType,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize)]
|
||||
pub struct PublicSchema(Schema);
|
||||
|
||||
impl PublicSchema {
|
||||
pub fn get_schema(&self) -> &Schema {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Schema> for PublicSchema {
|
||||
fn from(mut schema: Schema) -> Self {
|
||||
schema.user_attributes.attributes.extend_from_slice(&[
|
||||
AttributeSchema {
|
||||
name: "user_id".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: false,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "creation_date".to_owned(),
|
||||
attribute_type: AttributeType::DateTime,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: false,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "mail".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "uuid".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: false,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "display_name".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
]);
|
||||
schema
|
||||
.user_attributes
|
||||
.attributes
|
||||
.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
schema.group_attributes.attributes.extend_from_slice(&[
|
||||
AttributeSchema {
|
||||
name: "group_id".to_owned(),
|
||||
attribute_type: AttributeType::Integer,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: false,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "creation_date".to_owned(),
|
||||
attribute_type: AttributeType::DateTime,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: false,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "uuid".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: false,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "display_name".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
]);
|
||||
schema
|
||||
.group_attributes
|
||||
.attributes
|
||||
.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
PublicSchema(schema)
|
||||
}
|
||||
}
|
100
server/src/infra/test_utils.rs
Normal file
100
server/src/infra/test_utils.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use crate::domain::{error::Result, handler::*, opaque_handler::*, types::*};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::collections::HashSet;
|
||||
|
||||
mockall::mock! {
|
||||
pub TestBackendHandler{}
|
||||
impl Clone for TestBackendHandler {
|
||||
fn clone(&self) -> Self;
|
||||
}
|
||||
#[async_trait]
|
||||
impl LoginHandler for TestBackendHandler {
|
||||
async fn bind(&self, request: BindRequest) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl GroupListerBackendHandler for TestBackendHandler {
|
||||
async fn list_groups(&self, filters: Option<GroupRequestFilter>) -> Result<Vec<Group>>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl GroupBackendHandler for TestBackendHandler {
|
||||
async fn get_group_details(&self, group_id: GroupId) -> Result<GroupDetails>;
|
||||
async fn update_group(&self, request: UpdateGroupRequest) -> Result<()>;
|
||||
async fn create_group(&self, group_name: &str) -> Result<GroupId>;
|
||||
async fn delete_group(&self, group_id: GroupId) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl UserListerBackendHandler for TestBackendHandler {
|
||||
async fn list_users(&self, filters: Option<UserRequestFilter>, get_groups: bool) -> Result<Vec<UserAndGroups>>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl UserBackendHandler for TestBackendHandler {
|
||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User>;
|
||||
async fn create_user(&self, request: CreateUserRequest) -> Result<()>;
|
||||
async fn update_user(&self, request: UpdateUserRequest) -> Result<()>;
|
||||
async fn delete_user(&self, user_id: &UserId) -> Result<()>;
|
||||
async fn get_user_groups(&self, user_id: &UserId) -> Result<HashSet<GroupDetails>>;
|
||||
async fn add_user_to_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
||||
async fn remove_user_from_group(&self, user_id: &UserId, group_id: GroupId) -> Result<()>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl SchemaBackendHandler for TestBackendHandler {
|
||||
async fn get_schema(&self) -> Result<Schema>;
|
||||
}
|
||||
#[async_trait]
|
||||
impl BackendHandler for TestBackendHandler {}
|
||||
#[async_trait]
|
||||
impl OpaqueHandler for TestBackendHandler {
|
||||
async fn login_start(
|
||||
&self,
|
||||
request: login::ClientLoginStartRequest
|
||||
) -> Result<login::ServerLoginStartResponse>;
|
||||
async fn login_finish(&self, request: login::ClientLoginFinishRequest) -> Result<UserId>;
|
||||
async fn registration_start(
|
||||
&self,
|
||||
request: registration::ClientRegistrationStartRequest
|
||||
) -> Result<registration::ServerRegistrationStartResponse>;
|
||||
async fn registration_finish(
|
||||
&self,
|
||||
request: registration::ClientRegistrationFinishRequest
|
||||
) -> Result<()>;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup_default_schema(mock: &mut MockTestBackendHandler) {
|
||||
mock.expect_get_schema().returning(|| {
|
||||
Ok(Schema {
|
||||
user_attributes: AttributeList {
|
||||
attributes: vec![
|
||||
AttributeSchema {
|
||||
name: "avatar".to_owned(),
|
||||
attribute_type: AttributeType::JpegPhoto,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "first_name".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
AttributeSchema {
|
||||
name: "last_name".to_owned(),
|
||||
attribute_type: AttributeType::String,
|
||||
is_list: false,
|
||||
is_visible: true,
|
||||
is_editable: true,
|
||||
is_hardcoded: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
group_attributes: AttributeList {
|
||||
attributes: Vec::new(),
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue