Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show controls as read only based on tenant permissions #1472

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
87d40fa
Introduce read-only tenant mode logic
kajetan-nobel Jul 18, 2023
c38d4cc
Fix lint
kajetan-nobel Jul 18, 2023
54a6364
Merge branch 'main' into read-only-tenant-logic-poc
stephen-crawford Jul 27, 2023
da1720b
fix: typo in error message
kajetan-nobel Jul 28, 2023
bad9760
Merge remote-tracking branch 'origin/main' into read-only-tenant-logi…
kajetan-nobel Aug 4, 2023
bb2e4e5
Merge remote-tracking branch 'origin/main' into read-only-tenant-logi…
kajetan-nobel Aug 7, 2023
58a28d4
Merge remote-tracking branch 'origin/main' into read-only-tenant-logi…
kajetan-nobel Aug 16, 2023
feb6759
Merge remote-tracking branch 'origin/main' into read-only-tenant-logi…
kajetan-nobel Sep 5, 2023
8f97130
feat: move readonly tenant logic to a service
kajetan-nobel Sep 13, 2023
982ce6c
Merge remote-tracking branch 'origin/main' into read-only-tenant-logi…
kajetan-nobel Sep 13, 2023
31899ce
feat: extend cores readonly service
kajetan-nobel Sep 13, 2023
116dc99
Merge branch 'main' into read-only-tenant-logic-poc
kajetan-nobel Oct 10, 2023
9484a09
Merge branch 'main' into read-only-tenant-logic-poc
DarshitChanpura Oct 20, 2023
10c5784
feat: adds tests for readonly_service
kajetan-nobel Oct 25, 2023
8ec5413
Merge branch 'main' into read-only-tenant-logic-poc
kajetan-nobel Oct 25, 2023
798ca64
Apply suggestions from code review
kajetan-nobel Oct 30, 2023
e70efb5
feat: improve isAnonymousPage, add error log in isReadonly
kajetan-nobel Oct 31, 2023
69c6440
Merge branch 'main' into read-only-tenant-logic-poc
peternied Nov 7, 2023
569195d
Merge branch 'main' into HEAD
kajetan-nobel Nov 14, 2023
b52248e
Merge branch 'main' into read-only-tenant-logic-poc
stephen-crawford Nov 16, 2023
83bc08b
Merge branch 'main' into read-only-tenant-logic-poc
DarshitChanpura Nov 17, 2023
d902f1f
Merge branch 'main' into read-only-tenant-logic-poc
DarshitChanpura Nov 17, 2023
65646f5
Merge remote-tracking branch 'jakubp-eliatra/read-only-tenant-logic-p…
kajetan-nobel Nov 22, 2023
b5b9872
feat: add request.route.options.authRequired check
kajetan-nobel Nov 23, 2023
063664b
2feat: ReadonlyService is aware of disabled multitenancy through dash…
kajetan-nobel Nov 23, 2023
73e00c2
Merge branch 'main' into read-only-tenant-logic-poc
kajetan-nobel Nov 23, 2023
e1d41d4
refactor: remove unused imports, remove abstract from class due to du…
kajetan-nobel Nov 27, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions server/auth/types/authentication_type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ export interface IAuthenticationType {
type: string;
authHandler: AuthenticationHandler;
init: () => Promise<void>;
requestIncludesAuthInfo(request: OpenSearchDashboardsRequest): boolean;
cwperks marked this conversation as resolved.
Show resolved Hide resolved
buildAuthHeaderFromCookie(
cookie: SecuritySessionCookie,
request: OpenSearchDashboardsRequest
): any;
}

export type IAuthHandlerConstructor = new (
Expand Down
115 changes: 110 additions & 5 deletions server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import { first } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ResponseObject } from '@hapi/hapi';
import { merge } from 'lodash';
import {
PluginInitializerContext,
CoreSetup,
Expand All @@ -25,6 +25,9 @@ import {
ILegacyClusterClient,
SessionStorageFactory,
SharedGlobalConfig,
OpenSearchDashboardsRequest,
Capabilities,
CapabilitiesSwitcher,
} from '../../../src/core/server';

import { SecurityPluginSetup, SecurityPluginStart } from './types';
Expand All @@ -39,15 +42,13 @@ import {
ISavedObjectTypeRegistry,
} from '../../../src/core/server/saved_objects';
import { setupIndexTemplate, migrateTenantIndices } from './multitenancy/tenant_index';
import {
IAuthenticationType,
OpenSearchDashboardsAuthState,
} from './auth/types/authentication_type';
import { IAuthenticationType } from './auth/types/authentication_type';
import { getAuthenticationHandler } from './auth/auth_handler_factory';
import { setupMultitenantRoutes } from './multitenancy/routes';
import { defineAuthTypeRoutes } from './routes/auth_type_routes';
import { createMigrationOpenSearchClient } from '../../../src/core/server/saved_objects/migrations/core';
import { SecuritySavedObjectsClientWrapper } from './saved_objects/saved_objects_wrapper';
import { globalTenantName, isPrivateTenant } from '../common';
import { addTenantParameterToResolvedShortLink } from './multitenancy/tenant_resolver';

export interface SecurityPluginRequestContext {
Expand Down Expand Up @@ -86,6 +87,107 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
this.savedObjectClientWrapper = new SecuritySavedObjectsClientWrapper();
}

isAnonymousPage(request: OpenSearchDashboardsRequest) {
if (!request.headers || !request.headers.referer) {
return false;
}

try {
const url = new URL(request.headers.referer as string);
const pathsToIgnore = ['login', 'logout', 'customerror'];
return pathsToIgnore.includes(url.pathname?.split('/').pop() || '');
} catch (error: any) {
this.logger.error(`Could not parse the referer for the 1: ${error.stack}`);
DarshitChanpura marked this conversation as resolved.
Show resolved Hide resolved
}
}

isReadOnlyTenant(authInfo: any): boolean {
const currentTenant = authInfo.user_requested_tenant || globalTenantName;

// private tenant is not affected
if (isPrivateTenant(currentTenant)) {
return false;
}

return authInfo.tenants[currentTenant] !== true;
}

toggleReadOnlyCapabilities(capabilities: any): Partial<Capabilities> {
return Object.entries(capabilities).reduce((acc, cur) => {
const [key, value] = cur;

return { ...acc, [key]: capabilities.hide_for_read_only.includes(key) ? false : value };
}, {});
}

toggleForReadOnlyTenant(uiCapabilities: Capabilities): Partial<Capabilities> {
const defaultTenantOnlyCapabilities = Object.entries(uiCapabilities).reduce((acc, cur) => {
const [key, value] = cur;

if (!value.hide_for_read_only) {
return acc;
}

const updatedValue = this.toggleReadOnlyCapabilities(value);

return { ...acc, [key]: updatedValue };
}, {});

const finalCapabilities = merge(uiCapabilities, defaultTenantOnlyCapabilities);

return finalCapabilities;
}

capabilitiesSwitcher(
securityClient: SecurityClient,
auth: IAuthenticationType,
securitySessionStorageFactory: SessionStorageFactory<SecuritySessionCookie>
): CapabilitiesSwitcher {
return async (
request: OpenSearchDashboardsRequest,
uiCapabilities: Capabilities
): Promise<Partial<Capabilities>> => {
// omit for anonymous pages to avoid authentication errors
if (this.isAnonymousPage(request)) {
return uiCapabilities;
}

try {
const cookie = await securitySessionStorageFactory.asScoped(request).get();
let headers = request.headers;

if (!auth.requestIncludesAuthInfo(request) && cookie) {
headers = auth.buildAuthHeaderFromCookie(cookie, request);
}

const authInfo = await securityClient.authinfo(request, headers);

if (!authInfo.user_requested_tenant && cookie) {
authInfo.user_requested_tenant = cookie.tenant;
}

if (this.isReadOnlyTenant(authInfo)) {
return this.toggleForReadOnlyTenant(uiCapabilities);
}
} catch (error: any) {
this.logger.error(`Could not check auth info: ${error.stack}`);
}

return uiCapabilities;
};
}

registerSwitcher(
core: CoreSetup,
securityClient: SecurityClient,
auth: IAuthenticationType,
securitySessionStorageFactory: SessionStorageFactory<SecuritySessionCookie>
) {
core.capabilities.registerSwitcher(
this.capabilitiesSwitcher(securityClient, auth, securitySessionStorageFactory)
);
}

public async setup(core: CoreSetup) {
this.logger.debug('opendistro_security: Setup');

Expand Down Expand Up @@ -141,6 +243,9 @@ export class SecurityPlugin implements Plugin<SecurityPluginSetup, SecurityPlugi
// set up multi-tenent routes
if (config.multitenancy?.enabled) {
setupMultitenantRoutes(router, securitySessionStorageFactory, this.securityClient);

const securityClient: SecurityClient = this.securityClient;
this.registerSwitcher(core, securityClient, auth, securitySessionStorageFactory);
}

if (config.multitenancy.enabled && config.multitenancy.enable_aggregation_view) {
Expand Down