-
Notifications
You must be signed in to change notification settings - Fork 816
/
acm.ts
208 lines (189 loc) · 6.58 KB
/
acm.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import assert from 'assert';
import { InvalidDirectiveError, TransformerContractError } from '@aws-amplify/graphql-transformer-core';
type ACMConfig = {
resources: string[];
operations: string[];
name: string;
};
type SetRoleInput = {
role: string;
operations: Array<string>;
resource?: string;
};
type ValidateInput = {
role?: string;
resource?: string;
operations?: Array<string>;
};
type ResourceOperationInput = {
operations: Array<string>;
role?: string;
resource?: string;
};
/**
* Creates an access control matrix
* The following vectors are used
* - Roles
* - Resources
* - Operations
*/
export class AccessControlMatrix {
private name: string;
private roles: Array<string>;
private operations: Array<string>;
private resources: Array<string>;
private matrix: Array<Array<Array<boolean>>>;
constructor(config: ACMConfig) {
this.name = config.name;
this.operations = config.operations;
this.resources = config.resources;
this.matrix = new Array();
this.roles = new Array();
}
public setRole(input: SetRoleInput): void {
const { role, resource, operations } = input;
this.validate({ resource, operations });
let allowedVector: Array<Array<boolean>>;
if (!this.roles.includes(role)) {
allowedVector = this.getResourceOperationMatrix({ operations, resource });
this.roles.push(input.role);
this.matrix.push(allowedVector);
assert(this.roles.length === this.matrix.length, 'Roles are not aligned with Roles added in Matrix');
} else if (this.roles.includes(role) && resource) {
allowedVector = this.getResourceOperationMatrix({ operations, resource, role });
const roleIndex = this.roles.indexOf(role);
this.matrix[roleIndex] = allowedVector;
} else {
throw new InvalidDirectiveError(`@auth ${role} already exists for ${this.name}`);
}
}
public hasRole(role: string): boolean {
return this.roles.includes(role);
}
public getName(): string {
return this.name;
}
public getRoles(): Array<string> {
return this.roles;
}
public getResources(): Readonly<Array<string>> {
return this.resources;
}
public hasResource(resource: string): boolean {
return this.resources.includes(resource);
}
public isAllowed(role: string, resource: string, operation: string): boolean {
this.validate({ role, resource, operations: [operation] });
const roleIndex = this.roles.indexOf(role);
const resourceIndex = this.resources.indexOf(resource);
const operationIndex = this.operations.indexOf(operation);
return this.matrix[roleIndex][resourceIndex][operationIndex];
}
public resetAccessForResource(resource: string): void {
this.validate({ resource });
const resourceIndex = this.resources.indexOf(resource);
for (let i = 0; i < this.roles.length; i++) {
this.matrix[i][resourceIndex] = new Array(this.operations.length).fill(false);
}
}
/**
* Given an operation returns the roles which have access to at least one resource
* If fullAccess is true then it returns roles which have operation access on all resources
* @param operation string
* @param fullAccess boolean
* @returns array of roles
*/
public getRolesPerOperation(operation: string, fullAccess: boolean = false): Array<string> {
this.validate({ operations: [operation] });
const operationIndex = this.operations.indexOf(operation);
const roles = new Array<string>();
for (let x = 0; x < this.roles.length; x++) {
let hasOperation: boolean = false;
if (fullAccess) {
hasOperation = this.resources.every((resource, idx) => {
return this.matrix[x][idx][operationIndex];
});
} else {
hasOperation = this.resources.some((resource, idx) => {
return this.matrix[x][idx][operationIndex];
});
}
if (hasOperation) roles.push(this.roles[x]);
}
return roles;
}
/**
* @returns a map of role and their access
* this object can then be used in console.table()
*/
public getAcmPerRole(): Map<string, Object> {
const acmPerRole: Map<string, Object> = new Map();
for (let i = 0; i < this.matrix.length; i++) {
let tableObj: any = {};
for (let y = 0; y < this.matrix[i].length; y++) {
tableObj[this.resources[y]] = this.matrix[i][y].reduce((prev: any, resource: boolean, index: number) => {
prev[this.operations[index]] = resource;
return prev;
}, {});
}
acmPerRole.set(this.roles[i], tableObj);
}
return acmPerRole;
}
/**
* helpers
*/
private validate(input: ValidateInput) {
if (input.resource && !this.resources.includes(input.resource)) {
throw new TransformerContractError(`Resource: ${input.resource} is not configued in the ACM`);
}
if (input.role && !this.roles.includes(input.role)) {
throw new TransformerContractError(`Role: ${input.role} does not exist in ACM.`);
}
if (input.operations) {
input.operations.forEach(operation => {
if (this.operations.indexOf(operation) === -1)
throw new TransformerContractError(`Operation: ${operation} does not exist in the ACM.`);
});
}
}
/**
*
* if singular resource is specified the operations are only applied on the resource
* a denied array for every other resource is returned in the matrix
* @param operations
* @param resource
* @returns a 2d matrix containg the access for each resource
*/
private getResourceOperationMatrix(input: ResourceOperationInput): Array<Array<boolean>> {
const { operations, resource, role } = input;
let fieldAllowVector: boolean[][] = [];
let operationList: boolean[] = this.getOperationList(operations);
if (role && resource) {
const roleIndex = this.roles.indexOf(role);
const resourceIndex = this.resources.indexOf(resource);
fieldAllowVector = this.matrix[roleIndex];
fieldAllowVector[resourceIndex] = operationList;
return fieldAllowVector;
}
for (let i = 0; i < this.resources.length; i++) {
if (resource) {
if (this.resources.indexOf(resource) === i) {
fieldAllowVector.push(operationList);
} else {
fieldAllowVector.push(new Array(this.operations.length).fill(false));
}
} else {
fieldAllowVector.push(operationList);
}
}
return fieldAllowVector;
}
private getOperationList(operations: Array<string>): Array<boolean> {
let operationList: Array<boolean> = new Array();
for (let operation of this.operations) {
operationList.push(operations.includes(operation));
}
return operationList;
}
}