Преглед изворни кода

refs 124384: implement createUpdateExternalUserGroup

Futa Arai пре 2 година
родитељ
комит
6e25a513b2

+ 1 - 1
apps/app/src/interfaces/external-user-group.ts

@@ -4,7 +4,7 @@ import { IUserGroup } from './user';
 
 export interface IExternalUserGroup extends Omit<IUserGroup, 'parent'> {
   parent: Ref<IExternalUserGroup> | null
-  externalID: string // identifier used in external app/server
+  externalId: string // identifier used in external app/server
 }
 
 export interface IExternalUserGroupRelation extends Omit<IUserGroupRelation, 'relatedGroup'> {

+ 10 - 1
apps/app/src/server/models/external-user-group-relation.ts

@@ -7,7 +7,9 @@ import { getOrCreateModel } from '../util/mongoose-utils';
 
 export interface ExternalUserGroupRelationDocument extends IExternalUserGroupRelation, Document {}
 
-export type ExternalUserGroupRelationModel = Model<ExternalUserGroupRelationDocument>
+export interface ExternalUserGroupRelationModel extends Model<ExternalUserGroupRelationDocument> {
+  [x:string]: any, // for old methods
+}
 
 const schema = new Schema<ExternalUserGroupRelationDocument, ExternalUserGroupRelationModel>({
   relatedGroup: { type: Schema.Types.ObjectId, ref: 'ExternalUserGroup', required: true },
@@ -16,4 +18,11 @@ const schema = new Schema<ExternalUserGroupRelationDocument, ExternalUserGroupRe
   timestamps: { createdAt: true, updatedAt: false },
 });
 
+schema.statics.findOrCreateRelation = function(userGroup, user) {
+  return this.updateOne({
+    relatedGroup: { $eq: userGroup.id },
+    relatedUser: { $eq: user.id },
+  }, {}, { upsert: true });
+};
+
 export default getOrCreateModel<ExternalUserGroupRelationDocument, ExternalUserGroupRelationModel>('ExternalUserGroupRelation', schema);

+ 24 - 2
apps/app/src/server/models/external-user-group.ts

@@ -7,15 +7,37 @@ import { getOrCreateModel } from '../util/mongoose-utils';
 
 export interface ExternalUserGroupDocument extends IExternalUserGroup, Document {}
 
-export type ExternalUserGroupModel = Model<ExternalUserGroupDocument>
+export interface ExternalUserGroupModel extends Model<ExternalUserGroupDocument> {
+  [x:string]: any, // for old methods
+}
 
 const schema = new Schema<ExternalUserGroupDocument, ExternalUserGroupModel>({
   name: { type: String, required: true, unique: true },
   parent: { type: Schema.Types.ObjectId, ref: 'ExternalUserGroup', index: true },
   description: { type: String, default: '' },
-  externalID: { type: String, default: '' },
+  externalId: { type: String, required: true, unique: true },
 }, {
   timestamps: true,
 });
 
+schema.statics.createGroup = async function(name, description, externalId, parentId) {
+  // create without parent
+  if (parentId == null) {
+    return this.create({ name, description, externalId });
+  }
+
+  // create with parent
+  const parent = await this.findOne({ _id: parentId });
+  if (parent == null) {
+    throw Error('Parent does not exist.');
+  }
+  return this.create({
+    name, description, externalId, parent,
+  });
+};
+
+schema.statics.getByExternalIdOrCreateGroup = async function(name, description, externalId, parentId) {
+  return this.createGroup(name, description, externalId, parentId);
+};
+
 export default getOrCreateModel<ExternalUserGroupDocument, ExternalUserGroupModel>('ExternalUserGroup', schema);

+ 2 - 0
apps/app/src/server/routes/apiv3/external-user-group.ts

@@ -82,6 +82,8 @@ module.exports = (crowi: Crowi): Router => {
   router.put('/ldap/sync', loginRequiredStrictly, adminRequired, async(req: AuthorizedRequest, res: ApiV3Response) => {
     try {
       const ldapUserGroupSyncService = new LdapUserGroupSyncService(req.user.name, req.body.password);
+      console.log(await ldapUserGroupSyncService.getMemberUser('cn=customuser2,ou=users,dc=example,dc=org'));
+      // console.log(schema.statics);
     }
     catch (e) {
       res.apiv3Err(e, 500);

+ 29 - 34
apps/app/src/server/service/external-group/ldap-user-group-sync-service.ts

@@ -1,5 +1,7 @@
 import { IExternalUserGroup, LdapGroup } from '~/interfaces/external-user-group';
-import { IUser } from '~/interfaces/user';
+import { IUserHasId } from '~/interfaces/user';
+import ExternalUserGroup from '~/server/models/external-user-group';
+import ExternalUserGroupRelation from '~/server/models/external-user-group-relation';
 
 import { configManager } from '../config-manager';
 import ExternalAccountService from '../external-account';
@@ -51,14 +53,23 @@ class LdapUserGroupSyncService {
     */
   }
 
-  // // グループ生成/更新メソッド
-  // createUpdateExternalUserGroup(ldapGroup: LdapGroup): IExternalUserGroup {
-  //   /*
-  //    1. ldapGroup を元に ExternalUserGroup を生成/更新する
-  //    2. ldapGroup.users を元に ExternalUserGroup に所属していないメンバーについて getMemberUser を呼び出し、返却された各ユーザを ExternalUserGroup に所属させる (ExternalUserGroupRelation を生成する)
-  //    4. ExternalUserGroup を返却する
-  //   */
-  // }
+  /**
+   * 1. Create/Update ExternalUserGroup from ldapGroup
+   * 2. For every element in ldapGroup.users, call getMemberUser and create an ExternalUserGroupRelation with ExternalUserGroup if it does not have one
+   * 3. Retrun ExternalUserGroup
+  */
+  async createUpdateExternalUserGroup(ldapGroup: LdapGroup, parentId: string): Promise<IExternalUserGroup> {
+    const externalUserGroup = await ExternalUserGroup.createGroup(ldapGroup.name, ldapGroup.description, ldapGroup.dn, parentId);
+
+    await Promise.all(ldapGroup.users.map((userIdentifier) => {
+      return (async() => {
+        const user = await this.getMemberUser(userIdentifier);
+        await ExternalUserGroupRelation.findOrCreateRelation(externalUserGroup, user);
+      })();
+    }));
+
+    return externalUserGroup;
+  }
 
   /**
    * 1. Execute search on LDAP server for user using useridentifier
@@ -67,7 +78,7 @@ class LdapUserGroupSyncService {
    * @param {string} userIdentifier Search LDAP server using this identifier (DN or UID)
    * @returns {Promise<IUser | null>} IUser when found or created, null when neither
    */
-  async getMemberUser(userIdentifier: string): Promise<IUser | null> {
+  async getMemberUser(userIdentifier: string): Promise<IUserHasId | null> {
     const groupMembershipAttributeType = configManager?.getConfig('crowi', 'external-user-group:ldap:groupMembershipAttributeType');
 
     const getUser = async() => {
@@ -83,7 +94,7 @@ class LdapUserGroupSyncService {
 
     if (userEntryArr != null && userEntryArr.length > 0) {
       const userEntry = userEntryArr[0];
-      const uid = this.getStringValFromSearchResultEntry(userEntry, 'uid');
+      const uid = this.ldapService.getStringValFromSearchResultEntry(userEntry, 'uid');
       if (uid != null) {
         const externalAccount = await this.getExternalAccount(uid, userEntry);
         if (externalAccount != null) {
@@ -101,11 +112,11 @@ class LdapUserGroupSyncService {
     const groupNameAttribute = configManager.getConfig('crowi', 'external-user-group:ldap:groupNameAttribute');
     const groupDescriptionAttribute: string = configManager.getConfig('crowi', 'external-user-group:ldap:groupDescriptionAttribute');
 
-    const childGroups = this.getArrayValFromSearchResultEntry(groupEntry, groupChildGroupAttribute);
-    const users = this.getArrayValFromSearchResultEntry(groupEntry, groupMembershipAttribute);
+    const childGroups = this.ldapService.getArrayValFromSearchResultEntry(groupEntry, groupChildGroupAttribute);
+    const users = this.ldapService.getArrayValFromSearchResultEntry(groupEntry, groupMembershipAttribute);
 
-    const name = this.getStringValFromSearchResultEntry(groupEntry, groupNameAttribute);
-    const description = this.getStringValFromSearchResultEntry(groupEntry, groupDescriptionAttribute);
+    const name = this.ldapService.getStringValFromSearchResultEntry(groupEntry, groupNameAttribute);
+    const description = this.ldapService.getStringValFromSearchResultEntry(groupEntry, groupDescriptionAttribute);
 
     return name != null ? {
       dn: groupEntry.objectName || '',
@@ -123,9 +134,9 @@ class LdapUserGroupSyncService {
       const attrMapUsername = this.crowi.passportService.getLdapAttrNameMappedToUsername();
       const attrMapName = this.crowi.passportService.getLdapAttrNameMappedToName();
       const attrMapMail = this.crowi.passportService.getLdapAttrNameMappedToMail();
-      const usernameToBeRegistered = attrMapUsername === 'uid' ? uid : this.getStringValFromSearchResultEntry(userEntry, attrMapUsername);
-      const nameToBeRegistered = this.getStringValFromSearchResultEntry(userEntry, attrMapName);
-      const mailToBeRegistered = this.getStringValFromSearchResultEntry(userEntry, attrMapMail);
+      const usernameToBeRegistered = attrMapUsername === 'uid' ? uid : this.ldapService.getStringValFromSearchResultEntry(userEntry, attrMapUsername);
+      const nameToBeRegistered = this.ldapService.getStringValFromSearchResultEntry(userEntry, attrMapName);
+      const mailToBeRegistered = this.ldapService.getStringValFromSearchResultEntry(userEntry, attrMapMail);
 
       const userInfo = {
         id: uid,
@@ -141,22 +152,6 @@ class LdapUserGroupSyncService {
       .findOne({ providerType: 'ldap', accountId: uid });
   }
 
-  private getArrayValFromSearchResultEntry(entry: SearchResultEntry, attributeType: string) {
-    const values: string | string[] = entry.attributes.find(attribute => attribute.type === attributeType)?.values || [];
-    return typeof values === 'string' ? [values] : values;
-  }
-
-  private getStringValFromSearchResultEntry(entry: SearchResultEntry, attributeType: string): string | undefined {
-    const values: string | string[] | undefined = entry.attributes.find(attribute => attribute.type === attributeType)?.values;
-    if (typeof values === 'string' || values == null) {
-      return values;
-    }
-    if (values.length > 0) {
-      return values[0];
-    }
-    return undefined;
-  }
-
 }
 
 export default LdapUserGroupSyncService;

+ 16 - 0
apps/app/src/server/service/ldap.ts

@@ -115,6 +115,22 @@ class LdapService {
     return this.search(undefined, groupSearchBase);
   }
 
+  getArrayValFromSearchResultEntry(entry: SearchResultEntry, attributeType: string): string[] {
+    const values: string | string[] = entry.attributes.find(attribute => attribute.type === attributeType)?.values || [];
+    return typeof values === 'string' ? [values] : values;
+  }
+
+  getStringValFromSearchResultEntry(entry: SearchResultEntry, attributeType: string): string | undefined {
+    const values: string | string[] | undefined = entry.attributes.find(attribute => attribute.type === attributeType)?.values;
+    if (typeof values === 'string' || values == null) {
+      return values;
+    }
+    if (values.length > 0) {
+      return values[0];
+    }
+    return undefined;
+  }
+
 }
 
 export default LdapService;