Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
chore(bridge): Revert PR #6341 (#6585)
Browse files Browse the repository at this point in the history
  • Loading branch information
Kirdock authored Jan 14, 2022
1 parent 88c418b commit 71c1e19
Show file tree
Hide file tree
Showing 29 changed files with 314 additions and 777 deletions.
5 changes: 0 additions & 5 deletions bridge/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,3 @@ Thumbs.db
# e2e screenshots
e2e/screenshots/*
!e2e/screenshots/.gitkeep

# certificates
*.pem
*.crt
*.key
18 changes: 0 additions & 18 deletions bridge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,24 +75,6 @@ Per default login attempts are throttled to 10 requests within 60 minutes. This
- `REQUESTS_WITHIN_TIME` - how many login attempts in which timespan `REQUEST_TIME_LIMIT` (in minutes) are allowed per IP.
- `CLEAN_BUCKET_INTERVAL` - the interval (in minutes) the saved login attempts should be checked and deleted if the last request of an IP is older than `REQUEST_TIME_LIMIT` minutes. Default is 60 minutes.

### Setting up login via OpenID
To set up a login via OpenID you have to register an application by the identity provider you want in order to get an ID (`CLIENT_ID`) and a secret (`CLIENT_SECRET`).
After this is done the following environment variables have to be set:
- `OAUTH_ENABLED` - Flag to enable login via OpenID. To enable it set it to `true`.
- `OAUTH_BASE_URL` - URL of the bridge (e.g. `http://localhost:3000` or `https://myBridgeInstallation.com`).
- `OAUTH_DISCOVERY` - Discovery URL of the identity provider (e.g. https://api.login.yahoo.com/.well-known/openid-configuration).
- `OAUTH_CLIENT_ID` - Client ID.
- `OAUTH_CLIENT_SECRET` (optional) - Client secret. Some identity providers require using the client secret.
- `OAUTH_ID_TOKEN_ALG` (optional) - Algorithm that is used to verify the ID token (e.g. `ES256`). Default is `RS256`.
- `OAUTH_NAME_PROPERTY` (optional) - The property of the ID token that identifies the user. Default is `name` and fallback to `nickname`, `preferred_username` and `email`.

#### Additional information:
- Make sure you add the redirect URI `https://${yourDomain}/${pathToBridge}/oauth/redirect` to your identity provider.
- The identity provider has to support the grant types `authorization_code` and `refresh_token` and provide the endpoints `authorization_endpoint`, `token_endpoint` and `jwks_uri`.
- The refresh of the token is done by the bridge server on demand.
- If the identity provider provides the endpoint `end_session_endpoint`, it will be used for the logout.
- The bridge server itself is a confidential client.

### Custom Look And Feel

You can change the Look And Feel of the Keptn Bridge by creating a zip archive with your resources and make it downloadable from an URL.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
<div class="user">
<p>User: {{ user }}</p>
<div>
<form (ngSubmit)="logout($event)" method="POST" [action]="formData.end_session_endpoint">
<input type="hidden" name="state" [value]="formData.state" />
<input type="hidden" name="post_logout_redirect_uri" [value]="formData.post_logout_redirect_uri" />
<input type="hidden" name="id_token_hint" [value]="formData.id_token_hint" />
<button type="submit" dt-button>Logout</button>
</form>
<button (click)="logout()" dt-button>Logout</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { KtbUserComponent } from './ktb-user.component';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { AppModule } from '../../app.module';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { EndSessionData } from '../../../../shared/interfaces/end-session-data';
import { Location } from '@angular/common';
import { HttpClientTestingModule } from '@angular/common/http/testing';

describe('ktbUserComponentTest', () => {
let component: KtbUserComponent;
let fixture: ComponentFixture<KtbUserComponent>;
let httpMock: HttpTestingController;

beforeEach(async () => {
await TestBed.configureTestingModule({
Expand All @@ -17,33 +14,10 @@ describe('ktbUserComponentTest', () => {

fixture = TestBed.createComponent(KtbUserComponent);
component = fixture.componentInstance;
httpMock = TestBed.inject(HttpTestingController);
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeDefined();
});

it('should send POST to logout', () => {
const submitForm = { target: { submit: (): void => {} } };
const submitSpy = jest.spyOn(submitForm.target, 'submit');
component.logout(submitForm);
httpMock.expectOne('./logout').flush({
id_token_hint: '',
end_session_endpoint: '',
post_logout_redirect_uri: '',
state: '',
} as EndSessionData);
expect(submitSpy).toHaveBeenCalled();
});

it('should redirect to root if no data for logout is returned', () => {
const submitForm = { target: { submit: (): void => {} } };
const location = TestBed.inject(Location);
const locationSpy = jest.spyOn(location, 'prepareExternalUrl');
component.logout(submitForm);
httpMock.expectOne('./logout').flush(null);
expect(locationSpy).toBeCalledWith('/');
});
});
28 changes: 4 additions & 24 deletions bridge/client/app/_components/ktb-user/ktb-user.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { ChangeDetectorRef, Component, Input } from '@angular/core';
import { DataService } from '../../_services/data.service';
import { EndSessionData } from '../../../../shared/interfaces/end-session-data';
import { Component, Input } from '@angular/core';
import { Location } from '@angular/common';

@Component({
Expand All @@ -10,28 +8,10 @@ import { Location } from '@angular/common';
})
export class KtbUserComponent {
@Input() user?: string;
public formData: EndSessionData = {
state: '',
post_logout_redirect_uri: '',
end_session_endpoint: '',
id_token_hint: '',
};

constructor(
private readonly dataService: DataService,
private readonly _changeDetectorRef: ChangeDetectorRef,
private readonly location: Location
) {}
constructor(private readonly location: Location) {}

logout(submitEvent: { target: { submit: () => void } }): void {
this.dataService.logout().subscribe((response) => {
if (response) {
this.formData = response;
this._changeDetectorRef.detectChanges();
submitEvent.target.submit();
} else {
window.location.href = this.location.prepareExternalUrl('/');
}
});
logout(): void {
window.location.href = this.location.prepareExternalUrl('/logout');
}
}
5 changes: 0 additions & 5 deletions bridge/client/app/_services/api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { KeptnService } from '../../../shared/models/keptn-service';
import { ServiceState } from '../../../shared/models/service-state';
import { Deployment } from '../../../shared/interfaces/deployment';
import { IServiceRemediationInformation } from '../_interfaces/service-remediation-information';
import { EndSessionData } from '../../../shared/interfaces/end-session-data';

@Injectable({
providedIn: 'root',
Expand Down Expand Up @@ -477,8 +476,4 @@ export class ApiService {
services,
});
}

public logout(): Observable<EndSessionData | null> {
return this.http.get<EndSessionData | null>(`./logout`);
}
}
5 changes: 0 additions & 5 deletions bridge/client/app/_services/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import { Service } from '../_models/service';
import { Deployment } from '../_models/deployment';
import { ServiceState } from '../_models/service-state';
import { ServiceRemediationInformation } from '../_models/service-remediation-information';
import { EndSessionData } from '../../../shared/interfaces/end-session-data';

@Injectable({
providedIn: 'root',
Expand Down Expand Up @@ -743,8 +742,4 @@ export class DataService {
): Observable<Record<string, unknown>> {
return this.apiService.getIntersectedEvent(event, eventSuffix, projectName, stages, services);
}

public logout(): Observable<EndSessionData | null> {
return this.apiService.logout();
}
}
3 changes: 2 additions & 1 deletion bridge/server/.jest/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Express } from 'express';
import { AxiosInstance } from 'axios';

declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
interface Global {
app: Express;
baseUrl: string;
axiosInstance: AxiosInstance;
issuer?: unknown;
}
}
}
7 changes: 3 additions & 4 deletions bridge/server/.jest/setupServer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { init } from '../app';
import Axios from 'axios';
import https from 'https';
import { Express } from 'express';

const setupServer = async (): Promise<Express> => {
const setup = async (): Promise<void> => {
global.baseUrl = 'http://localhost/api/';

global.axiosInstance = Axios.create({
Expand All @@ -17,7 +16,7 @@ const setupServer = async (): Promise<Express> => {
},
});

return init();
global.app = await init();
};

export { setupServer };
export default setup();
1 change: 1 addition & 0 deletions bridge/server/.jest/shutdownServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
process.exit();
67 changes: 34 additions & 33 deletions bridge/server/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { execSync } from 'child_process';
import { AxiosError } from 'axios';
import { EnvironmentUtils } from './utils/environment.utils';
import { ClientFeatureFlags, ServerFeatureFlags } from './feature-flags';
import { setupOAuth } from './user/oauth';

// eslint-disable-next-line @typescript-eslint/naming-convention
const __dirname = dirname(fileURLToPath(import.meta.url));
const app = express();
const apiUrl: string | undefined = process.env.API_URL;
let apiToken: string | undefined = process.env.API_TOKEN;
let cliDownloadLink: string | undefined = process.env.CLI_DOWNLOAD_LINK;
Expand All @@ -30,20 +30,19 @@ const throttleBucket: { [ip: string]: number[] } = {};
const rootFolder = join(__dirname, '../../../');
const serverFolder = join(rootFolder, 'server');
const oneWeek = 7 * 24 * 3_600_000; // 3600000msec == 1hour
const serverFeatureFlags = new ServerFeatureFlags();
const clientFeatureFlags = new ClientFeatureFlags();
EnvironmentUtils.setFeatureFlags(process.env, serverFeatureFlags);
EnvironmentUtils.setFeatureFlags(process.env, clientFeatureFlags);

if (process.env.NODE_ENV !== 'test') {
setupDefaultLookAndFeel();
}
if (lookAndFeelUrl) {
setupLookAndFeel(lookAndFeelUrl);
}

async function init(): Promise<Express> {
const app = express();
const serverFeatureFlags = new ServerFeatureFlags();
const clientFeatureFlags = new ClientFeatureFlags();
EnvironmentUtils.setFeatureFlags(process.env, serverFeatureFlags);
EnvironmentUtils.setFeatureFlags(process.env, clientFeatureFlags);

if (process.env.NODE_ENV !== 'test') {
setupDefaultLookAndFeel();
}
if (lookAndFeelUrl) {
setupLookAndFeel(lookAndFeelUrl);
}
if (!apiUrl) {
throw Error('API_URL is not provided');
}
Expand Down Expand Up @@ -114,7 +113,7 @@ async function init(): Promise<Express> {
// Remove the X-Powered-By headers, has to be done via express and not helmet
app.disable('x-powered-by');

const authType: string = await setAuth(app, serverFeatureFlags.OAUTH_ENABLED);
const authType: string = await setAuth();

// everything starting with /api is routed to the api implementation
app.use('/api', apiRouter({ apiUrl, apiToken, cliDownloadLink, integrationsPageLink, authType, clientFeatureFlags }));
Expand All @@ -135,25 +134,27 @@ async function init(): Promise<Express> {
return app;
}

async function setOAUTH(app: Express): Promise<void> {
const errorSuffix =
'must be defined when oauth based login (OAUTH_ENABLED) is activated.' +
' Please check your environment variables.';
async function setOAUTH(): Promise<void> {
const sessionRouter = (await import('./user/session.js')).sessionRouter(app);
const oauthRouter = await (await import('./user/oauth.js')).oauthRouter;
const authCheck = (await import('./user/session.js')).isAuthenticated;

if (!process.env.OAUTH_DISCOVERY) {
throw Error(`OAUTH_DISCOVERY ${errorSuffix}`);
}
if (!process.env.OAUTH_CLIENT_ID) {
throw Error(`OAUTH_CLIENT_ID ${errorSuffix}`);
}
if (!process.env.OAUTH_BASE_URL) {
throw Error(`OAUTH_B_URL ${errorSuffix}`);
}
// Initialise session middleware
app.use(sessionRouter);
// Initializing OAuth middleware.
app.use(oauthRouter);

await setupOAuth(app, process.env.OAUTH_DISCOVERY, process.env.OAUTH_CLIENT_ID, process.env.OAUTH_BASE_URL);
// Authentication filter for API requests
app.use('/api', (req, resp, next) => {
if (!authCheck(req)) {
next({ response: { status: 401 } });
return;
}
return next();
});
}

async function setBasisAUTH(app: Express): Promise<void> {
async function setBasisAUTH(): Promise<void> {
console.error('Installing Basic authentication - please check environment variables!');

setInterval(cleanIpBuckets, cleanBucketsInterval);
Expand Down Expand Up @@ -185,14 +186,14 @@ async function setBasisAUTH(app: Express): Promise<void> {
});
}

async function setAuth(app: Express, oAuthEnabled: boolean): Promise<string> {
async function setAuth(): Promise<string> {
let authType;
if (oAuthEnabled) {
await setOAUTH(app);
if (serverFeatureFlags.OAUTH_ENABLED) {
await setOAUTH();
authType = 'OAUTH';
} else if (process.env.BASIC_AUTH_USERNAME && process.env.BASIC_AUTH_PASSWORD) {
authType = 'BASIC';
await setBasisAUTH(app);
await setBasisAUTH();
} else {
authType = 'NONE';
console.log('Not installing authentication middleware');
Expand Down
1 change: 0 additions & 1 deletion bridge/server/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ declare global {
namespace NodeJS {
interface Global {
axiosInstance?: AxiosInstance;
issuer?: unknown;
}
}
}
2 changes: 2 additions & 0 deletions bridge/server/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export default {
collectCoverage: true,
coverageDirectory: '<rootDir>/coverage',
setupFiles: ['<rootDir>/.jest/setEnvVars.ts'],
setupFilesAfterEnv: ['<rootDir>/.jest/setupServer.ts'],
globalTeardown: '<rootDir>/.jest/shutdownServer.ts',
testPathIgnorePatterns: ['<rootDir>/dist', '<rootDir>/node_modules'],
};
1 change: 0 additions & 1 deletion bridge/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"helmet": "4.6.0",
"memorystore": "1.6.6",
"morgan": "1.10.0",
"openid-client": "5.1.0",
"pug": "3.0.2",
"semver": "7.3.5",
"yaml": "1.10.2"
Expand Down
10 changes: 1 addition & 9 deletions bridge/server/test/bridge-info.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,8 @@
import request from 'supertest';
import { setupServer } from '../.jest/setupServer';
import { Express } from 'express';

describe('Test /bridgeInfo', () => {
let app: Express;

beforeAll(async () => {
app = await setupServer();
});

it('should return bridgeInfo', async () => {
const response = await request(app).get('/api/bridgeInfo');
const response = await request(global.app).get('/api/bridgeInfo');
expect(response.body).toEqual({
bridgeVersion: 'develop',
apiUrl: global.baseUrl,
Expand Down
Loading

0 comments on commit 71c1e19

Please sign in to comment.