Skip to content
This repository has been archived by the owner on Aug 16, 2024. It is now read-only.

Commit

Permalink
Merge pull request #3 from JupiterOne/sq-user-group-model-step
Browse files Browse the repository at this point in the history
Add user group entity
  • Loading branch information
Kenan Warren authored Apr 29, 2021
2 parents 439a884 + 4587323 commit d901909
Show file tree
Hide file tree
Showing 15 changed files with 523 additions and 23 deletions.
7 changes: 4 additions & 3 deletions docs/jupiterone.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ https://github.com/JupiterOne/sdk/blob/master/docs/integrations/development.md

The following entities are created:

| Resources | Entity `_type` | Entity `_class` |
| --------- | ------------------- | --------------- |
| Project | `sonarqube_project` | `Project` |
| Resources | Entity `_type` | Entity `_class` |
| --------- | ---------------------- | --------------- |
| Project | `sonarqube_project` | `Project` |
| UserGroup | `sonarqube_user_group` | `UserGroup` |

<!--
********************************************************************************
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,24 +54,19 @@
"response": {
"bodySize": 648,
"content": {
"_isBinary": true,
"mimeType": "text/html; charset=UTF-8",
"size": 648,
"text": "[\"1f8b0800c215a85d00037d544d73db2010bdfb576cd54b32232427691a8f2d69fa99690f690f690f3d12b1b298085001c9f674f2dfbb428e2337999a91815d78bbefb190bd12a6f4bb16a1f6aa2966d963875c1433a05fe6a56fb0f8bce5aa6d103e19c5a5ced2d13a1b9728f41cca9a5b873e8f3a5fb14504693171d6deb70c7f77b2cfa38f467bd49e0d612328c7591e79dcfa7408bf3a40bd84a4b9c23cea256e5a63fd64ff460a5fe7027b59220b9318a4965ef286b99237989f3d4139bf23324306fbc0a573d1e8bb3362077fc2304c7979bfb6a6d38295a6317609afab39b5f3d56189e2762df512e64fa6960b21f5fac85651a6ace24a36bb2530de929ccced9c4715c3d8b34ec6f0a191fafe8697b7c1744d9b62886e716d107e7e8d68fcbd450db75cbb61f2059b1ebd2c397cc30ec97230c4f0de1277c2a6a5cca195d5532e61f010fe85ec277483704b783b9fb7dbe70c2f5101efbc7981e839aad57f4513d42619dc192bd032cb85ec1ce9945c1e01982d7335176643c8ed162ee8bba26f18dbf51d3f99c7a125f3f3d3d5840c5f0eeac5d4f7d2498f6242ed31938bc59bc56292c970fe4c60692cf7d2104b6d344e41df291492c389e25bb6d7e76ad0e774027eace25155fca3d844e563c7c3246496860a2dc25965e9781d67d9509b743b29d8be8eebb36737934ca3af2d7ed4d2810876a051652c740e6198354de7fc40b847c011c10d0e7a0e3a45f7c925f0cb74446217b678421aab658fa6a1217909a0b348747c6d3a0fad9514a23474b452073181e6dcdd538584e82d5a259d234792a5ed21cf8c1e088b551e0dcf845ba6e966b34924d73c31769d8e215dba4f332a6e8c1d4810a00a419284d0781110b3348893a57ba9d2f141fb0b28eb7c6fe8040000\"]"
"text": "<!doctype html>\n<html>\n<head>\n <title>Example Domain</title>\n\n <meta charset=\"utf-8\" />\n <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <style type=\"text/css\">\n body {\n background-color: #f0f0f2;\n margin: 0;\n padding: 0;\n font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n \n }\n div {\n width: 600px;\n margin: 5em auto;\n padding: 2em;\n background-color: #fdfdff;\n border-radius: 0.5em;\n box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n }\n a:link, a:visited {\n color: #38488f;\n text-decoration: none;\n }\n @media (max-width: 700px) {\n div {\n margin: 0 auto;\n width: auto;\n }\n }\n </style> \n</head>\n\n<body>\n<div>\n <h1>Example Domain</h1>\n <p>This domain is for use in illustrative examples in documents. You may use this\n domain in literature without prior coordination or asking for permission.</p>\n <p><a href=\"https://www.iana.org/domains/example\">More information...</a></p>\n</div>\n</body>\n</html>\n"
},
"cookies": [],
"headers": [
{
"name": "content-encoding",
"value": "gzip"
},
{
"name": "accept-ranges",
"value": "bytes"
},
{
"name": "age",
"value": "2781"
"value": "16484"
},
{
"name": "cache-control",
Expand All @@ -83,11 +78,11 @@
},
{
"name": "date",
"value": "Wed, 28 Apr 2021 13:52:10 GMT"
"value": "Wed, 28 Apr 2021 17:40:33 GMT"
},
{
"name": "expires",
"value": "Wed, 05 May 2021 13:52:10 GMT"
"value": "Wed, 05 May 2021 17:40:33 GMT"
},
{
"name": "last-modified",
Expand All @@ -114,22 +109,22 @@
"value": "close"
}
],
"headersSize": 358,
"headersSize": 359,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 404,
"statusText": "Not Found"
},
"startedDateTime": "2021-04-28T13:52:10.264Z",
"time": 54,
"startedDateTime": "2021-04-28T17:40:33.678Z",
"time": 41,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 54
"wait": 41
}
}
],
Expand Down
63 changes: 60 additions & 3 deletions src/provider/SonarqubeClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import {
} from '@jupiterone/integration-sdk-testing';

import { createSonarqubeClient } from '.';
import { SonarqubeProject } from './types';
import { SonarqubeProject, SonarqubeUserGroup } from './types';

describe('#iterateProjects', () => {
describe('#SonarqubeClient', () => {
let recording: Recording;

afterEach(async () => {
Expand All @@ -19,7 +19,7 @@ describe('#iterateProjects', () => {
test('should fail with invalid token', async () => {
recording = setupRecording({
directory: __dirname,
name: 'iterateProjectsShouldFailWithInvalidToken',
name: 'SonarqubeClientShouldFailWithInvalidToken',
options: {
matchRequestsBy: {
url: {
Expand All @@ -44,6 +44,14 @@ describe('#iterateProjects', () => {
}),
).rejects.toThrowError(IntegrationProviderAuthenticationError);
});
});

describe('#iterateProjects', () => {
let recording: Recording;

afterEach(async () => {
await recording.stop();
});

test('should fetch projects with valid config', async () => {
recording = setupRecording({
Expand Down Expand Up @@ -87,3 +95,52 @@ describe('#iterateProjects', () => {
);
});
});

describe('#iterateUserGroups', () => {
let recording: Recording;

afterEach(async () => {
await recording.stop();
});

test('should fetch user groups with valid config', async () => {
recording = setupRecording({
directory: __dirname,
name: 'iterateUserGroupsShouldFetchUserGroupsWithValidConfig',
options: {
matchRequestsBy: {
url: {
hostname: false,
},
},
recordFailedRequests: true,
},
mutateEntry: mutations.unzipGzippedRecordingEntry,
});

const context = createMockStepExecutionContext({
instanceConfig: {
baseUrl: process.env.BASE_URL || 'http://localhost:9000',
apiToken: process.env.API_TOKEN || 'string-value',
},
});
const provider = createSonarqubeClient(context.instance.config);

const results: SonarqubeUserGroup[] = [];
await provider.iterateUserGroups((userGroup) => {
results.push(userGroup);
});

expect(results).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
name: expect.any(String),
description: expect.any(String),
membersCount: expect.any(Number),
default: expect.any(Boolean),
}),
]),
);
});
});
11 changes: 11 additions & 0 deletions src/provider/SonarqubeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
SonarqubeProject,
PaginatedResponse,
ValidationResponse,
SonarqubeUserGroup,
} from './types';

/**
Expand Down Expand Up @@ -49,6 +50,16 @@ export class SonarqubeClient {
);
}

async iterateUserGroups(
iteratee: ResourceIteratee<SonarqubeUserGroup>,
): Promise<void> {
return this.iterateResources<'groups', SonarqubeUserGroup>(
'/user_groups/search',
'groups',
iteratee,
);
}

async fetchAuthenticationValidate(): Promise<ValidationResponse> {
return this.makeSingularRequest('/authentication/validate') as Promise<
ValidationResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"log": {
"_recordingName": "iterateProjectsShouldFailWithInvalidToken",
"_recordingName": "SonarqubeClientShouldFailWithInvalidToken",
"creator": {
"comment": "persister:JupiterOneIntegationFSPersister",
"name": "Polly.JS",
Expand Down Expand Up @@ -86,7 +86,7 @@
},
{
"name": "date",
"value": "Wed, 28 Apr 2021 13:52:10 GMT"
"value": "Thu, 29 Apr 2021 12:57:16 GMT"
},
{
"name": "connection",
Expand All @@ -99,7 +99,7 @@
"status": 401,
"statusText": "Unauthorized"
},
"startedDateTime": "2021-04-28T13:52:10.247Z",
"startedDateTime": "2021-04-29T12:57:16.568Z",
"time": 20,
"timings": {
"blocked": -1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"log": {
"_recordingName": "iterateUserGroupsShouldFetchUserGroupsWithValidConfig",
"creator": {
"comment": "persister:JupiterOneIntegationFSPersister",
"name": "Polly.JS",
"version": "4.3.0"
},
"entries": [
{
"_id": "8a49b5d504f234197e1cd616efb83230",
"_order": 0,
"cache": {},
"request": {
"bodySize": 0,
"cookies": [],
"headers": [
{
"_fromType": "array",
"name": "authorization",
"value": "[REDACTED]"
},
{
"_fromType": "array",
"name": "accept",
"value": "*/*"
},
{
"_fromType": "array",
"name": "user-agent",
"value": "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)"
},
{
"_fromType": "array",
"name": "accept-encoding",
"value": "gzip,deflate"
},
{
"_fromType": "array",
"name": "connection",
"value": "close"
},
{
"name": "host",
"value": "localhost:9000"
}
],
"headersSize": 313,
"httpVersion": "HTTP/1.1",
"method": "GET",
"queryString": [
{
"name": "page",
"value": "1"
},
{
"name": "per_page",
"value": "100"
}
],
"url": "http://localhost:9000/api/user_groups/search?page=1&per_page=100"
},
"response": {
"bodySize": 349,
"content": {
"mimeType": "application/json",
"size": 349,
"text": "{\"paging\":{\"pageIndex\":1,\"pageSize\":100,\"total\":2},\"groups\":[{\"id\":\"AXkPpNolV6WlOKn23U7S\",\"name\":\"sonar-administrators\",\"description\":\"System administrators\",\"membersCount\":1,\"default\":false},{\"id\":\"AXkPpNolV6WlOKn23U7T\",\"name\":\"sonar-users\",\"description\":\"Any new users created will automatically join this group\",\"membersCount\":2,\"default\":true}]}"
},
"cookies": [],
"headers": [
{
"name": "x-frame-options",
"value": "SAMEORIGIN"
},
{
"name": "x-xss-protection",
"value": "1; mode=block"
},
{
"name": "x-content-type-options",
"value": "nosniff"
},
{
"name": "cache-control",
"value": "no-cache, no-store, must-revalidate"
},
{
"name": "content-type",
"value": "application/json"
},
{
"name": "content-length",
"value": "349"
},
{
"name": "date",
"value": "Wed, 28 Apr 2021 16:41:24 GMT"
},
{
"name": "connection",
"value": "close"
}
],
"headersSize": 258,
"httpVersion": "HTTP/1.1",
"redirectURL": "",
"status": 200,
"statusText": "OK"
},
"startedDateTime": "2021-04-28T16:41:24.511Z",
"time": 19,
"timings": {
"blocked": -1,
"connect": -1,
"dns": -1,
"receive": 0,
"send": 0,
"ssl": -1,
"wait": 19
}
}
],
"pages": [],
"version": "1.2"
}
}
8 changes: 8 additions & 0 deletions src/provider/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ export interface SonarqubeProject {
revision: string;
}

export interface SonarqubeUserGroup {
id: string; // This value is unique but is NOT what ties relationships to users
name: string; // This value is unique and is what ties relationships to users
description: string;
membersCount: number;
default: boolean;
}

export interface Pagination {
pageIndex: number;
pageSize: number;
Expand Down
6 changes: 6 additions & 0 deletions src/steps/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const Steps = {
PROJECTS: 'fetch-projects',
USER_GROUPS: 'fetch-user-groups',
};

export const Entities = {
Expand All @@ -8,4 +9,9 @@ export const Entities = {
_type: 'sonarqube_project',
_class: ['Project'],
},
USER_GROUP: {
resourceName: 'UserGroup',
_type: 'sonarqube_user_group',
_class: ['UserGroup'],
},
};
3 changes: 2 additions & 1 deletion src/steps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {

import { SonarqubeIntegrationConfig } from '../types';
import { projectSteps } from './project';
import { userGroupSteps } from './user-group';

const integrationSteps: Step<
IntegrationStepExecutionContext<SonarqubeIntegrationConfig>
>[] = [...projectSteps];
>[] = [...projectSteps, ...userGroupSteps];

export { integrationSteps };
Loading

0 comments on commit d901909

Please sign in to comment.