mirror of
				https://github.com/gristlabs/grist-core.git
				synced 2025-06-13 20:53:59 +00:00 
			
		
		
		
	Implement ingress
This commit is contained in:
		
							parent
							
								
									98f50d3c81
								
							
						
					
					
						commit
						c38107aadc
					
				| @ -541,6 +541,10 @@ export class HomeDBManager extends EventEmitter { | ||||
|     return this._usersManager.deleteUser(scope, userIdToDelete, name); | ||||
|   } | ||||
| 
 | ||||
|   public async overrideUser(userId: number, props: UserProfile) { | ||||
|     return this._usersManager.overrideUser(userId, props); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Returns a QueryResult for the given organization.  The orgKey | ||||
|    * can be a string (the domain from url) or the id of an org.  If it is | ||||
|  | ||||
| @ -386,7 +386,7 @@ export class UsersManager { | ||||
|         // Set the user's name if our provider knows it. Otherwise use their username
 | ||||
|         // from email, for lack of something better. If we don't have a profile at this
 | ||||
|         // time, then leave the name blank in the hopes of learning it when the user logs in.
 | ||||
|         user.name = (profile && (profile.name || email.split('@')[0])) || ''; | ||||
|         user.name = (profile && this._getNameOrDeduceFromEmail(profile.name, email)) || ''; | ||||
|         needUpdate = true; | ||||
|       } | ||||
|       if (!user.picture && profile && profile.picture) { | ||||
| @ -561,6 +561,27 @@ export class UsersManager { | ||||
|       .filter(fullProfile => fullProfile); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Update users with passed property. Optional user properties that are missing will be reset to their default value. | ||||
|    */ | ||||
|   public async overrideUser(userId: number, props: UserProfile): Promise<User> { | ||||
|     return await this._connection.transaction(async manager => { | ||||
|       const user = await this.getUser(userId, {includePrefs: true}); | ||||
|       if (!user) { throw new ApiError("unable to find user to update", 404); } | ||||
|       const login = user.logins[0]; | ||||
|       user.name = this._getNameOrDeduceFromEmail(props.name, props.email); | ||||
|       user.picture = props.picture || ''; | ||||
|       user.options = {...(user.options || {}), locale: props.locale ?? undefined}; | ||||
|       if (props.email) { | ||||
|         login.email = normalizeEmail(props.email); | ||||
|         login.displayEmail = props.email; | ||||
|       } | ||||
|       await manager.save([user, login]); | ||||
| 
 | ||||
|       return (await this.getUser(userId))!; | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * ================================== | ||||
|    * | ||||
| @ -748,6 +769,10 @@ export class UsersManager { | ||||
|     return id; | ||||
|   } | ||||
| 
 | ||||
|   private _getNameOrDeduceFromEmail(name: string, email: string) { | ||||
|     return name || email.split('@')[0]; | ||||
|   } | ||||
| 
 | ||||
|   // This deals with the problem posed by receiving a PermissionDelta specifying a
 | ||||
|   // role for both alice@x and Alice@x. We do not distinguish between such emails.
 | ||||
|   // If there are multiple indistinguishabe emails, we preserve just one of them,
 | ||||
|  | ||||
| @ -1,5 +1,7 @@ | ||||
| import { normalizeEmail } from "app/common/emails"; | ||||
| import { UserProfile } from "app/common/LoginSessionAPI"; | ||||
| import { User } from "app/gen-server/entity/User.js"; | ||||
| import { SCIMMY } from "scimmy-routers"; | ||||
| import SCIMMY from "scimmy"; | ||||
| 
 | ||||
| /** | ||||
|  * Converts a user from your database to a SCIMMY user | ||||
| @ -8,7 +10,7 @@ export function toSCIMMYUser(user: User) { | ||||
|   if (!user.logins) { | ||||
|     throw new Error("User must have at least one login"); | ||||
|   } | ||||
|   const preferredLanguage = user.options?.locale ?? "en"; | ||||
|   const locale = user.options?.locale ?? "en"; | ||||
|   return new SCIMMY.Schemas.User({ | ||||
|     id: String(user.id), | ||||
|     userName: user.loginEmail, | ||||
| @ -16,16 +18,29 @@ export function toSCIMMYUser(user: User) { | ||||
|     name: { | ||||
|       formatted: user.name, | ||||
|     }, | ||||
|     preferredLanguage, | ||||
|     locale: preferredLanguage, // Assume locale is the same as preferredLanguage
 | ||||
|     locale, | ||||
|     preferredLanguage: locale, // Assume preferredLanguage is the same as locale
 | ||||
|     photos: user.picture ? [{ | ||||
|       value: user.picture, | ||||
|       type: "photo", | ||||
|       primary: true | ||||
|     }] : undefined, | ||||
|     emails: [{ | ||||
|       value: user.loginEmail, | ||||
|       value: user.logins[0].displayEmail, | ||||
|       primary: true, | ||||
|     }], | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| export function toUserProfile(scimUser: any, existingUser?: User): UserProfile { | ||||
|   const emailValue = scimUser.emails?.[0]?.value; | ||||
|   if (emailValue && normalizeEmail(emailValue) !== normalizeEmail(scimUser.userName)) { | ||||
|     throw new SCIMMY.Types.Error(400, 'invalidValue', 'Email and userName must be the same'); | ||||
|   } | ||||
|   return { | ||||
|     name: scimUser.displayName ?? existingUser?.name, | ||||
|     picture: scimUser.photos?.[0]?.value, | ||||
|     locale: scimUser.locale, | ||||
|     email: emailValue ?? scimUser.userName ?? existingUser?.loginEmail, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @ -4,12 +4,13 @@ import SCIMMY from "scimmy"; | ||||
| import SCIMMYRouters from "scimmy-routers"; | ||||
| import { RequestWithLogin } from '../../Authorizer'; | ||||
| import { InstallAdmin } from '../../InstallAdmin'; | ||||
| import { toSCIMMYUser } from './ScimUserUtils'; | ||||
| import { toSCIMMYUser, toUserProfile } from './ScimUserUtils'; | ||||
| import { ApiError } from 'app/common/ApiError'; | ||||
| 
 | ||||
| const WHITELISTED_PATHS_FOR_NON_ADMINS = [ "/Me", "/Schemas", "/ResourceTypes", "/ServiceProviderConfig" ]; | ||||
| 
 | ||||
| async function isAuthorizedAction(mreq: RequestWithLogin, installAdmin: InstallAdmin): Promise<boolean> { | ||||
|   const isAdmin = await installAdmin.isAdminReq(mreq) | ||||
|   const isAdmin = await installAdmin.isAdminReq(mreq); | ||||
|   const isScimUser = Boolean(process.env.GRIST_SCIM_EMAIL && mreq.user?.loginEmail === process.env.GRIST_SCIM_EMAIL); | ||||
|   return isAdmin || isScimUser || WHITELISTED_PATHS_FOR_NON_ADMINS.includes(mreq.path); | ||||
| } | ||||
| @ -30,18 +31,39 @@ const buildScimRouterv2 = (dbManager: HomeDBManager, installAdmin: InstallAdmin) | ||||
|       const scimmyUsers = (await dbManager.getUsers()).map(user => toSCIMMYUser(user)); | ||||
|       return filter ? filter.match(scimmyUsers) : scimmyUsers; | ||||
|     }, | ||||
|     ingress: async (resource: any) => { | ||||
|     ingress: async (resource: any, data: any) => { | ||||
|       try { | ||||
|         const { id } = resource; | ||||
|         if (id) { | ||||
|           return null; | ||||
|           const updatedUser = await dbManager.overrideUser(id, toUserProfile(data)); | ||||
|           return toSCIMMYUser(updatedUser); | ||||
|         } | ||||
|         return []; | ||||
|         const userProfileToInsert = toUserProfile(data); | ||||
|         const maybeExistingUser = await dbManager.getExistingUserByLogin(userProfileToInsert.email); | ||||
|         if (maybeExistingUser !== undefined) { | ||||
|           throw new SCIMMY.Types.Error(409, 'uniqueness', 'An existing user with the passed email exist.'); | ||||
|         } | ||||
|         const newUser = await dbManager.getUserByLoginWithRetry(userProfileToInsert.email, { | ||||
|           profile: userProfileToInsert | ||||
|         }); | ||||
|         return toSCIMMYUser(newUser!); | ||||
|       } catch (ex) { | ||||
|         // FIXME: remove this
 | ||||
|         if (Math.random() > 1) { | ||||
|           return null; | ||||
|         if (ex instanceof ApiError) { | ||||
|           if (ex.status === 409) { | ||||
|             throw new SCIMMY.Types.Error(ex.status, 'uniqueness', ex.message); | ||||
|           } | ||||
|           throw new SCIMMY.Types.Error(ex.status, null!, ex.message); | ||||
|         } | ||||
| 
 | ||||
|         if (ex.code?.startsWith('SQLITE')) { | ||||
|           switch (ex.code) { | ||||
|             case 'SQLITE_CONSTRAINT': | ||||
|               throw new SCIMMY.Types.Error(409, 'uniqueness', ex.message); | ||||
|             default: | ||||
|               throw new SCIMMY.Types.Error(500, 'serverError', ex.message); | ||||
|           } | ||||
|         } | ||||
| 
 | ||||
|         throw ex; | ||||
|       } | ||||
|     }, | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user