Merged PR 220: [12-2]RoleGuradにTierに対するチェックを実装する

## 概要
[Task1951: [12-2]RoleGuradにTierに対するチェックを実装する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1951)

- 階層の指定について宣言的にできるように対応しました。
- 階層の定数について配列化しました。
(分かりやすさとroleguards側の実装しやすさのため)
- 使用していない宣言がいくつか見られたので、気づいた範囲で削除しました。
- 影響範囲(他の機能にも影響があるか)
 - これまで処理内で階層のチェックを行っていた箇所について、宣言的にチェックするよう修正しました。
  修正対象の洗い出しについては「補足」参照
 - 階層のみチェックする場合を考慮し、既存のrolesに対するチェックを任意指定にしています。
  これに伴い、rolesが指定されなかった場合を考慮して修正を行っています。

## レビューポイント
- 各コントローラを見ていただき、階層チェックのやり方について使いづらさがないか

## UIの変更
- 無し

## 動作確認状況
- ローカルで確認済

## 補足
- 修正対象の洗い出しは以下の通り実施しています。
https://ndstokyo.sharepoint.com/:u:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%83%A9%E3%83%95%E3%82%B9%E3%82%B1%E3%83%83%E3%83%81/PBI1189_%5B%E9%9A%8E%E5%B1%A4%E5%91%A8%E3%82%8A%E6%95%B4%E7%90%86%5D%E7%AC%AC%E4%B8%80%EF%BD%9E%E7%AC%AC%E5%9B%9B%E9%9A%8E%E5%B1%A4%E3%81%A8%E3%81%97%E3%81%A6%E3%80%81%E3%83%91%E3%83%BC%E3%83%88%E3%83%8A%E3%83%BC%E5%90%91%E3%81%91Web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AB%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%97%E3%81%9F%E3%81%84.drawio?csf=1&web=1&e=h3Sbf6
This commit is contained in:
masaaki 2023-07-12 02:06:16 +00:00
parent 6f92313e9a
commit 9c0f457e9f
8 changed files with 106 additions and 63 deletions

View File

@ -21,4 +21,4 @@ export const HEADER_NAME = "ODMS Cloud";
/**
* adminのみに表示するヘッダータブ
*/
export const ADMIN_ONLY_TABS = [HEADER_MENUS_LICENSE];
export const ADMIN_ONLY_TABS = [HEADER_MENUS_LICENSE, HEADER_MENUS_USER];

View File

@ -1,4 +1,4 @@
import { ADMIN_ROLES, USER_ROLES } from '../../../constants';
import { ADMIN_ROLES, USER_ROLES, TIERS } from '../../../constants';
import { RoleGuard } from './roleguards';
describe('RoleGuard', () => {
@ -105,3 +105,18 @@ describe('isRoles', () => {
expect(result).toBeFalsy();
});
});
describe('RoleGuard(Tier)', () => {
it('設定したTierに指定した値が含まれる場合、許可される', () => {
const guards = RoleGuard.requireds({
tiers: [TIERS.TIER1, TIERS.TIER2, TIERS.TIER3],
});
expect(guards.checkTier(TIERS.TIER1)).toBeTruthy();
});
it('設定したTierに指定した値が含まれない場合、拒否される', () => {
const guards = RoleGuard.requireds({
tiers: [TIERS.TIER1, TIERS.TIER2, TIERS.TIER3],
});
expect(guards.checkTier(TIERS.TIER4)).toBeFalsy();
});
});

View File

@ -10,9 +10,11 @@ import { Request } from 'express';
import { retrieveAuthorizationToken } from '../../../common/http/helper';
import { makeErrorResponse } from '../../../common/error/makeErrorResponse';
import { Roles } from '../../types/role';
import { ADMIN_ROLES, USER_ROLES } from '../../../constants';
import { Tiers } from '../../types/tier';
import { ADMIN_ROLES, USER_ROLES, TIERS } from '../../../constants';
export interface RoleSetting {
roles: (Roles | Roles[])[];
roles?: (Roles | Roles[])[];
tiers?: Tiers[];
}
export class RoleGuard implements CanActivate {
@ -45,15 +47,34 @@ export class RoleGuard implements CanActivate {
return true;
}
if (!this.isRoles(payload.role)) {
// 値チェック
// settingsで設定されていない場合チェック自体行わないので初期値はtrueとする
let isRoleValid = true;
let isTierValid = true;
if (this.settings.roles) {
isRoleValid = this.isRoles(payload.role);
}
if (this.settings.tiers) {
isTierValid = this.isTier(payload.tier);
}
if (!isRoleValid || !isTierValid) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const isValid = this.checkRole(payload.role);
if (isValid) {
// 権限チェック
// settingsで設定されていない場合チェック自体行わないので初期値はtrueとする
let hasRolePermission = true;
let hasTierPermission = true;
if (this.settings.roles) {
hasRolePermission = this.checkRole(payload.role);
}
if (this.settings.tiers) {
hasTierPermission = this.checkTier(payload.tier);
}
if (hasRolePermission && hasTierPermission) {
return true;
}
@ -131,4 +152,29 @@ export class RoleGuard implements CanActivate {
guard.settings = settings;
return guard;
}
/**
* Tierの値の妥当性チェック
* @param tier tierの値
* @returns true/false
*/
private isTier = (tier: number): boolean => {
//値のチェック
return Object.values(TIERS).includes(
tier as (typeof TIERS)[keyof typeof TIERS],
);
};
/**
* publicメソッドとして切り出したもの
*
* @param tier tierの値
* @returns true/false
*/
checkTier(tier: number): boolean {
const { tiers } = this.settings;
// 宣言された階層中にパラメータの内容が含まれていればtrue
return tiers.includes(tier as (typeof TIERS)[keyof typeof TIERS]);
}
}

View File

@ -0,0 +1,6 @@
import { TIERS } from '../../../constants';
/**
* Token.tierに配置されうる数値リテラル型
*/
export type Tiers = (typeof TIERS)[keyof typeof TIERS];

View File

@ -1,32 +1,19 @@
/**
* OMDS東京
*
* @const {number}
*/
export const TIER_1 = 1;
/**
* OMDS現地法人
* @const {number}
*/
export const TIER_2 = 2;
/**
*
* @const {number}
*/
export const TIER_3 = 3;
/**
*
* @const {number}
*/
export const TIER_4 = 4;
/**
*
* @const {number}
*/
export const TIER_5 = 5;
export const TIERS = {
//OMDS東京
TIER1: 1,
//OMDS現地法人
TIER2: 2,
//代理店
TIER3: 3,
//販売店
TIER4: 4,
//エンドユーザー
TIER5: 5,
} as const;
/**
* East USに保存する国リスト

View File

@ -1,7 +1,6 @@
import {
Body,
Controller,
HttpException,
HttpStatus,
Post,
Get,
@ -28,15 +27,11 @@ import {
CreatePartnerAccountRequest,
CreatePartnerAccountResponse,
} from './types/types';
import { USER_ROLES, ADMIN_ROLES } from '../../constants';
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
//import { CryptoService } from '../../gateways/crypto/crypto.service';
import { retrieveAuthorizationToken } from '../../common/http/helper';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { isVerifyError, verify } from '../../common/jwt';
import { AccessToken } from '../../common/token';
//import { confirmPermission } from '../../common/auth/auth';
import jwt from 'jsonwebtoken';
@ApiTags('accounts')
@ -112,7 +107,9 @@ export class AccountsController {
})
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@UseGuards(
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], tiers: [TIERS.TIER5] }),
)
@Post('licenses/summary')
async getLicenseSummary(
@Req() req: Request,

View File

@ -10,7 +10,7 @@ import {
} from '../../gateways/adb2c/adb2c.service';
import { Account } from '../../repositories/accounts/entity/account.entity';
import { User } from '../../repositories/users/entity/user.entity';
import { LICENSE_EXPIRATION_THRESHOLD_DAYS, TIER_5 } from '../../constants';
import { LICENSE_EXPIRATION_THRESHOLD_DAYS, TIERS } from '../../constants';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { TypistGroup } from './types/types';
import { GetLicenseSummaryResponse, Typist } from './types/types';
@ -147,7 +147,7 @@ export class AccountsService {
companyName,
country,
dealerAccountId,
TIER_5,
TIERS.TIER5,
externalUser.sub,
role,
acceptedTermsVersion,

View File

@ -5,7 +5,6 @@ import {
Post,
Req,
UseGuards,
HttpException,
} from '@nestjs/common';
import {
ApiResponse,
@ -28,9 +27,8 @@ import { retrieveAuthorizationToken } from '../../common/http/helper';
import { AccessToken } from '../../common/token';
import { AuthGuard } from '../../common/guards/auth/authguards';
import { RoleGuard } from '../../common/guards/role/roleguards';
import { ADMIN_ROLES, TIER_1, TIER_5 } from '../../constants';
import { ADMIN_ROLES, TIERS } from '../../constants';
import jwt from 'jsonwebtoken';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
@ApiTags('licenses')
@Controller('licenses')
@ -59,7 +57,12 @@ export class LicensesController {
@ApiOperation({ operationId: 'createOrders' })
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@UseGuards(
RoleGuard.requireds({
roles: [ADMIN_ROLES.ADMIN],
tiers: [TIERS.TIER2, TIERS.TIER3, TIERS.TIER4, TIERS.TIER5],
}),
)
@Post('/orders')
async createOrders(
@Req() req: Request,
@ -99,7 +102,9 @@ export class LicensesController {
@ApiOperation({ operationId: 'issueCardLicenses' })
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@UseGuards(
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], tiers: [TIERS.TIER1] }),
)
@Post('/cards')
async issueCardLicenses(
@Req() req: Request,
@ -111,13 +116,6 @@ export class LicensesController {
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// 第一階層以外は401を返す後々UseGuardsで弾く
if (payload.tier != TIER_1) {
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
);
}
const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
payload.userId,
body.createCount,
@ -150,7 +148,9 @@ export class LicensesController {
@ApiOperation({ operationId: 'activateCardLicenses' })
@ApiBearerAuth()
@UseGuards(AuthGuard)
@UseGuards(RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN] }))
@UseGuards(
RoleGuard.requireds({ roles: [ADMIN_ROLES.ADMIN], tiers: [TIERS.TIER5] }),
)
@Post('/cards/activate')
async activateCardLicenses(
@Req() req: Request,
@ -162,14 +162,6 @@ export class LicensesController {
const accessToken = retrieveAuthorizationToken(req);
const payload = jwt.decode(accessToken, { json: true }) as AccessToken;
// TODO 第五階層以外は401を返す後々UseGuardsで弾く
if (payload.tier != TIER_5) {
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
);
}
await this.licensesService.activateCardLicenseKey(
payload.userId,
body.cardLicenseKey,