Skip to content

Commit

Permalink
🐞Export of roles was failing due to 1-level Membership Criteria
Browse files Browse the repository at this point in the history
  • Loading branch information
yannick-beot-sp committed Dec 14, 2023
1 parent 14c01fd commit 311ba2a
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 70 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ None

## Release Notes

- Add the command "Ping Cluster" on sources (cf. [#61](https://github.com/yannick-beot-sp/vscode-sailpoint-identitynow/pull/61))
- Export of roles was failing due to 1-level Membership Criteria

### 0.0.25

- Add test connection (cf. [#58](https://github.com/yannick-beot-sp/vscode-sailpoint-identitynow/issues/58))
Expand Down
13 changes: 4 additions & 9 deletions src/parser/RoleMembershipSelectorConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,19 @@ export class RoleMembershipSelectorConverter implements Visitor<RoleCriteriaLeve
await val.accept(this, arg);

if (this.root?.children === undefined || this.root?.children.length === 0) {
// We have only 1 level so far. Needs to create 2 levels artifically
// We have only 1 level so far. Needs to create at least 2 levels artifically
this.root = {
operation: "OR",
children: [
{
operation: "AND",
children: [this.root]
}
]

children: [this.root]
};
} else if (this.root.children[0].children === undefined || this.root.children[0].children.length === 0) {
} else if (this.root.children.every(x => x.children === undefined || x.children.length === 0)) {
// We have already 2 level2. Needs to create 1 level artifically
this.root = {
operation: this.root.operation === "AND" ? "OR" : "AND",
children: [this.root]
};
}

}

async visitAttribute(val: Attribute, arg: RoleCriteriaLevel1): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class Parser {
if (isLogicalOperation(token)) {
const currentOperator = token.toLowerCase() === "and" ? "AND" : "OR";
if (operator !== undefined && operator !== currentOperator){
throw new ParseException("All operators should be either \"sand\" or \"or\"");
throw new ParseException("All operators should be either \"and\" or \"or\"");
}
operator = currentOperator;

Expand Down
6 changes: 4 additions & 2 deletions src/parser/roleMembershipSelectorToStringConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function roleMembershipSelectorToStringConverter(
return await convertRoleCriteriaLevel2(roleCriteriaLevel1.children[0], sourceIdToName);
}

if (isLogicalOperation(roleCriteriaLevel1.children[0].operation)) {
if (roleCriteriaLevel1.children.some(c => isLogicalOperation(c.operation))) {
return join(roleCriteriaLevel1.operation,
(await Promise.all(
roleCriteriaLevel1.children
Expand All @@ -44,7 +44,9 @@ function join(op: RoleCriteriaOperation, expressions: string[]) {
async function convertRoleCriteriaLevel2(
roleCriteriaLevel2: RoleCriteriaLevel2,
sourceIdToName: CacheService<string>): Promise<string> {
if (roleCriteriaLevel2.children === null) {
if (roleCriteriaLevel2.children === null
|| roleCriteriaLevel2.children === undefined ||
roleCriteriaLevel2.children.length === 0) {
return await convertRoleCriteriaLevel3(roleCriteriaLevel2, sourceIdToName);
}

Expand Down
201 changes: 147 additions & 54 deletions src/test/unit/RoleMembershipSelectorConverter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,20 @@ class MockupCache extends CacheService<string>{

suite('RoleMembershipSelectorConverter Test Suite', () => {
const parser = new Parser();

describe('1 level conversion', async () => {

const expectedComparisonOperator: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [
{
"operation": "AND",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.department",
sourceId: undefined
},
"stringValue": "Customer Service"
}
]
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.department",
sourceId: undefined
},
"stringValue": "Customer Service"
}
]
};
Expand Down Expand Up @@ -67,8 +63,84 @@ suite('RoleMembershipSelectorConverter Test Suite', () => {
assert.deepEqual(converter.root, expectedComparisonOperator);
});
});

describe('2 level conversion', async () => {
it("should parse an expression with 2 comparisons and convert it", async () => {
const expectedComparisonOperator2: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [{
"operation": "AND",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.department",
sourceId: undefined
},
"stringValue": "Customer Service"
},
{
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.cloudLifecycleState",
"sourceId": undefined
},
"stringValue": "active",
},
]
}]
};

const converter = new RoleMembershipSelectorConverter(new MockupCache());

const expression = parser.parse("identity.department eq 'Customer Service' and identity.cloudLifecycleState eq 'active'");

await converter.visitExpression(expression, undefined);

assert.deepEqual(converter.root, expectedComparisonOperator2);
});
it("should parse an expression with 2 non-identity and convert it", async () => {
const expectedComparisonOperator3: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [{
"operation": "AND",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "ENTITLEMENT",
"property": "attribute.memberOf",
"sourceId": "6ba6925ebc1a4e5d98ca6fd3fc542ea4"
},
"stringValue": "CN=Accounting,OU=Groups,OU=Demo,DC=seri,DC=sailpointdemo,DC=com",
},
{
"operation": "EQUALS",
"key": {
"type": "ACCOUNT",
"property": "attribute.departmentNumber",
"sourceId": "6ba6925ebc1a4e5d98ca6fd3fc542ea4"
},
"stringValue": "1234",
},
]
}]
};

const converter = new RoleMembershipSelectorConverter(new MockupCache());

const expression = parser.parse("'Active Directory'.entitlement.memberOf EQ \"CN=Accounting,OU=Groups,OU=Demo,DC=seri,DC=sailpointdemo,DC=com\" and 'Active Directory'.attribute.departmentNumber eq \"1234\"");

await converter.visitExpression(expression, undefined);

assert.deepEqual(converter.root, expectedComparisonOperator3);
});
});

describe('3 level conversion', async () => {
it("should parse an expression and convert it", async () => {
const expectedComparisonOperator2: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [
Expand All @@ -94,85 +166,105 @@ suite('RoleMembershipSelectorConverter Test Suite', () => {
"stringValue": "active",
},
]
}
]
};

const converter = new RoleMembershipSelectorConverter(new MockupCache());

const expression = parser.parse("identity.department eq 'Customer Service' and identity.cloudLifecycleState eq 'active'");

await converter.visitExpression(expression, undefined);

assert.deepEqual(converter.root, expectedComparisonOperator2);
});
it("should parse an expression with 2 non-identity and convert it", async () => {
const expectedComparisonOperator3: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [
},
{
"operation": "AND",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "ENTITLEMENT",
"property": "attribute.memberOf",
"sourceId": "6ba6925ebc1a4e5d98ca6fd3fc542ea4"
"type": "IDENTITY",
"property": "attribute.cloudLifecycleState",
"sourceId": undefined
},
"stringValue": "CN=Accounting,OU=Groups,OU=Demo,DC=seri,DC=sailpointdemo,DC=com",
"stringValue": "active",
},
{
"operation": "EQUALS",
"operation": "CONTAINS",
"key": {
"type": "ACCOUNT",
"property": "attribute.departmentNumber",
"sourceId": "6ba6925ebc1a4e5d98ca6fd3fc542ea4"
"type": "IDENTITY",
"property": "attribute.jobTitle",
"sourceId": undefined
},
"stringValue": "1234",
},
"stringValue": "Accounts Payable Analyst",
}
]
}
]
}
};

const converter = new RoleMembershipSelectorConverter(new MockupCache());

const expression = parser.parse("'Active Directory'.entitlement.memberOf EQ \"CN=Accounting,OU=Groups,OU=Demo,DC=seri,DC=sailpointdemo,DC=com\" and 'Active Directory'.attribute.departmentNumber eq \"1234\"");
const expression = parser.parse("(identity.department eq 'Customer Service' and identity.cloudLifecycleState eq 'active') OR (identity.cloudLifecycleState eq 'active' AND identity.jobTitle co 'Accounts Payable Analyst')");

await converter.visitExpression(expression, undefined);

assert.deepEqual(converter.root, expectedComparisonOperator3);
assert.deepEqual(converter.root, expectedComparisonOperator2);
});

});

describe('3 level conversion', async () => {
it("should parse an expression and convert it", async () => {
describe('unbalanced level conversion', async () => {
it("should parse an expression and convert it with parenthesis", async () => {
const expectedComparisonOperator2: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "ENTITLEMENT",
"property": "attribute.ProfileId",
"sourceId": "6ba6925ebc1a4e5d98ca6fd3fc542ea4"
},
"stringValue": "00e1i000000eM2qAAE"
},
{
"operation": "AND",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.department",
sourceId: undefined
"property": "attribute.cloudLifecycleState",
"sourceId": undefined
},
"stringValue": "Customer Service"
"stringValue": "active",
},
{
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.cloudLifecycleState",
"property": "attribute.usertype",
"sourceId": undefined
},
"stringValue": "active",
},
"stringValue": "External",
}
]
}
]
};

const converter = new RoleMembershipSelectorConverter(new MockupCache());

const expression = parser.parse("('Active Directory'.entitlement.ProfileId eq '00e1i000000eM2qAAE') or (identity.cloudLifecycleState eq 'active' and identity.usertype eq 'External')");

await converter.visitExpression(expression, undefined);

assert.deepEqual(converter.root, expectedComparisonOperator2);
});

it("should parse an expression and convert it without parenthesis", async () => {
const expectedComparisonOperator2: RoleCriteriaLevel1 = {
"operation": "OR",
"children": [
{
"operation": "EQUALS",
"key": {
"type": "ENTITLEMENT",
"property": "attribute.ProfileId",
"sourceId": "6ba6925ebc1a4e5d98ca6fd3fc542ea4"
},
"stringValue": "00e1i000000eM2qAAE"
},
{
"operation": "AND",
Expand All @@ -187,27 +279,28 @@ suite('RoleMembershipSelectorConverter Test Suite', () => {
"stringValue": "active",
},
{
"operation": "CONTAINS",
"operation": "EQUALS",
"key": {
"type": "IDENTITY",
"property": "attribute.jobTitle",
"property": "attribute.usertype",
"sourceId": undefined
},
"stringValue": "Accounts Payable Analyst",
"stringValue": "External",
}
]
}
]
};
};;

const converter = new RoleMembershipSelectorConverter(new MockupCache());

const expression = parser.parse("(identity.department eq 'Customer Service' and identity.cloudLifecycleState eq 'active') OR (identity.cloudLifecycleState eq 'active' AND identity.jobTitle co 'Accounts Payable Analyst')");
const expression = parser.parse("'Active Directory'.entitlement.ProfileId eq '00e1i000000eM2qAAE' or (identity.cloudLifecycleState eq 'active' and identity.usertype eq 'External')");

await converter.visitExpression(expression, undefined);

assert.deepEqual(converter.root, expectedComparisonOperator2);
});

});

});
Loading

0 comments on commit 311ba2a

Please sign in to comment.