forked from janus-idp/backstage-plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #47 from mareklibra/FLPATH-809.Authentication
FLPATH-809: get the user via Identity API
- Loading branch information
Showing
13 changed files
with
350 additions
and
181 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './service/router'; | ||
export * from './service/permissions'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { AuthenticationError, NotAllowedError } from '@backstage/errors'; | ||
import { getBearerTokenFromAuthorizationHeader } from '@backstage/plugin-auth-node'; | ||
import { | ||
AuthorizeResult, | ||
BasicPermission, | ||
PermissionEvaluator, | ||
} from '@backstage/plugin-permission-common'; | ||
|
||
import express from 'express'; | ||
|
||
import { DefaultServiceUser } from './constants'; | ||
import { RouterOptions } from './types'; | ||
|
||
export type GetLoggedInUserOptions = Pick< | ||
RouterOptions, | ||
'identity' | 'tokenManager' | 'externalCallerSecret' | ||
>; | ||
export type CheckUserPermission = GetLoggedInUserOptions & | ||
Pick<RouterOptions, 'permissions'>; | ||
|
||
/* | ||
* User's entity must be present in the catalog. | ||
*/ | ||
export const getLoggedInUser = async ( | ||
request: express.Request, | ||
{ identity, tokenManager, externalCallerSecret }: GetLoggedInUserOptions, | ||
): Promise<string> => { | ||
const identityResponse = await identity.getIdentity({ request }); | ||
|
||
// To properly set identity, see packages/backend/src/plugins/auth.ts or https://backstage.io/docs/auth/identity-resolver | ||
if (identityResponse) { | ||
// The auth token contains user's identity, most probably originated in the FE | ||
// Example: user:default/guest | ||
let author = identityResponse?.identity.userEntityRef; | ||
if (author) { | ||
if (author.startsWith('user:')) { | ||
author = author.slice('user:'.length); | ||
} | ||
} else { | ||
throw new AuthenticationError( | ||
'Missing valid authentication data or the user is not in the Catalog.', | ||
); | ||
} | ||
return author; | ||
} | ||
|
||
const token = getBearerTokenFromAuthorizationHeader( | ||
request.header('authorization'), | ||
); | ||
if (token) { | ||
// backend service-to-service flow | ||
await tokenManager.authenticate(token); | ||
} | ||
|
||
// External call - workaround | ||
// Following shared-secret is a workaround till we make the creation of valid JWT tokens by external callers simple. | ||
// In such case, the flow would be identical with the service-to-service. | ||
// https://github.com/backstage/backstage/issues/18622 | ||
// https://github.com/backstage/backstage/issues/9374 | ||
if (externalCallerSecret) { | ||
if (request.header('notifications-secret') === externalCallerSecret) { | ||
return DefaultServiceUser; | ||
} | ||
throw new AuthenticationError('Provided shared secret does not match.'); | ||
} | ||
|
||
// Since the shared secret has not been requested in the configuration, we ALLOW the request | ||
return DefaultServiceUser; | ||
}; | ||
|
||
export const checkPermission = async ( | ||
request: express.Request, | ||
permissions: PermissionEvaluator, | ||
permission: BasicPermission, | ||
loggedInUser: string, | ||
) => { | ||
const token = getBearerTokenFromAuthorizationHeader( | ||
request.header('authorization'), | ||
); | ||
const decision = ( | ||
await permissions.authorize([{ permission }], { | ||
token, | ||
}) | ||
)[0]; | ||
|
||
if (decision.result === AuthorizeResult.DENY) { | ||
throw new NotAllowedError( | ||
`The user ${loggedInUser} is not authorized to ${permission.name}`, | ||
); | ||
} | ||
}; | ||
|
||
/** | ||
* Checks if the logged-in user has the required permission | ||
* and returns the username. | ||
*/ | ||
export const checkUserPermission = async ( | ||
request: express.Request, | ||
options: CheckUserPermission, | ||
requiredPermission: BasicPermission, | ||
): Promise<string> => { | ||
const loggedInUser = await getLoggedInUser(request, options); | ||
await checkPermission( | ||
request, | ||
options.permissions, | ||
requiredPermission, | ||
loggedInUser, | ||
); | ||
return loggedInUser; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const DefaultServiceUser = 'default/externalServiceNotificationsUser'; | ||
export const DefaultMessageScope = 'user'; | ||
export const DefaultPageNumber = 1; | ||
export const DefaultPageSize = 20; | ||
export const DefaultOrderBy = 'created'; | ||
export const DefaultOrderDirection = 'desc'; |
Oops, something went wrong.