Merge pull request #1333 from spotify/identity-static-adapter

Add static json adapter to serve user groups
This commit is contained in:
Raghunandan Balachandran
2020-06-17 14:44:07 +02:00
committed by GitHub
5 changed files with 185 additions and 7 deletions
@@ -0,0 +1,89 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
Group,
GroupsJson,
GroupsResponse,
IdentityApi,
GroupsRequest,
} from './types';
import fs from 'fs-extra';
import path from 'path';
const GROUPS_JSON_FILE = path.join(__dirname, 'data', 'userGroups.json');
export class StaticJsonAdapter implements IdentityApi {
private readonly groups: Group[];
constructor() {
const groupsJson: GroupsJson = fs.readJsonSync(GROUPS_JSON_FILE, {
encoding: 'utf8',
});
this.groups = groupsJson.groups;
}
getUserGroups(req: GroupsRequest): Promise<GroupsResponse> {
return new Promise(resolve => {
const { user, type } = req;
const userGroups = this._getUserGroups(this.groups, user);
const groups = this.filterGroupsByType(userGroups, type);
resolve({ groups });
});
}
_getUserGroups(groups: Group[], user: string) {
const userGroups: Set<Group> = new Set();
groups.forEach(group => {
if (this.isUserInGroup(group, user)) {
userGroups.add(group);
}
if (group.children) {
const userSubGroups = this._getUserGroups(group.children, user) ?? [];
const isUserInSubGroup = Boolean(userSubGroups.length);
if (isUserInSubGroup) {
userGroups.add(group);
}
userSubGroups.forEach(subGroup => userGroups.add(subGroup));
}
});
return Array.from(userGroups);
}
private filterGroupsByType = (userGroups: Group[], type: string) => {
const groups = type
? userGroups
.filter((group: Group) => group.type === type)
.map(group => ({ name: group.name, type: group.type }))
: userGroups.map(group => ({
name: group.name,
type: group.type,
}));
return groups;
};
private isUserInGroup = (group: Group, user: string): boolean => {
if (group.members) {
const groupMembers = group.members;
const groupsWithUser = groupMembers.filter(
member => member.name === user,
);
return Boolean(groupsWithUser.length);
}
return false;
};
}
@@ -0,0 +1,20 @@
{
"groups": [
{
"name": "engineering",
"type": "org",
"children": [
{
"name": "authentication",
"type": "team",
"members": [{ "name": "kent" }, { "name": "dobbs" }]
},
{
"name": "checkout",
"type": "team",
"members": [{ "name": "don" }, { "name": "abramev" }]
}
]
}
]
}
@@ -0,0 +1,17 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './StaticJsonAdapter';
@@ -0,0 +1,42 @@
/*
* Copyright 2020 Spotify AB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type User = {
name: string;
};
export type Group = {
name: string;
type: string;
members?: User[];
children?: Group[];
};
export type GroupsJson = {
groups: Group[];
};
export type GroupsResponse = {
groups: Group[];
};
export type GroupsRequest = {
user: string;
type: string;
};
export interface IdentityApi {
getUserGroups(req: GroupsRequest): Promise<GroupsResponse>;
}
+17 -7
View File
@@ -17,21 +17,31 @@
import express from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import { StaticJsonAdapter } from '../adapters';
import { IdentityApi } from '../adapters/types';
export interface RouterOptions {
logger: Logger;
}
const makeRouter = (adapter: IdentityApi): express.Router => {
const router = Router();
router.get('/users/:user/groups', async (req, res) => {
const user = req.params.user;
const type = req.query.type?.toString() ?? '';
const response = await adapter.getUserGroups({ user, type });
res.send(response);
});
return router;
};
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const router = Router();
const logger = options.logger;
router.use('/ping', (_, res) => {
logger.info('heartbeat for identity service');
res.send('pong');
});
return router;
logger.info('Initializing identity API backend');
const adapter = new StaticJsonAdapter();
return makeRouter(adapter);
}