Skip to content

Commit

Permalink
feat(api-mailer): add security permissions and checks (#2733)
Browse files Browse the repository at this point in the history
  • Loading branch information
brunozoric committed Oct 25, 2022
1 parent 3337e33 commit 57df8ee
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 69 deletions.
47 changes: 1 addition & 46 deletions packages/api-mailer/__tests__/context/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
import { SecurityIdentity } from "@webiny/api-security/types";
export { until } from "@webiny/project-utils/testing/helpers/until";
export { sleep } from "@webiny/project-utils/testing/helpers/sleep";

export interface PermissionsArg {
name: string;
locales?: string[];
rwd?: string;
pw?: string | null;
own?: boolean;
}

export const identity = {
Expand All @@ -16,53 +11,13 @@ export const identity = {
type: "admin"
};

const getSecurityIdentity = () => {
return identity;
};

export const createPermissions = (permissions?: PermissionsArg[]): PermissionsArg[] => {
if (permissions) {
return permissions;
}
return [
{
name: "cms.settings"
},
{
name: "cms.contentModel",
rwd: "rwd"
},
{
name: "cms.contentModelGroup",
rwd: "rwd"
},
{
name: "cms.contentEntry",
rwd: "rwd",
pw: "rcpu"
},
{
name: "cms.endpoint.read"
},
{
name: "cms.endpoint.manage"
},
{
name: "cms.endpoint.preview"
},
{
name: "content.i18n",
locales: ["en-US"]
},
{
name: "apw.publishingWorkflows"
name: "mailer.settings"
}
];
};

export const createIdentity = (identity?: SecurityIdentity) => {
if (!identity) {
return getSecurityIdentity();
}
return identity;
};
81 changes: 81 additions & 0 deletions packages/api-mailer/__tests__/settings.crud.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,85 @@ describe("Settings Transporter CRUD", () => {
host: "dummy-host2.webiny"
});
});

it("should be possible to access settings when no permissions", async () => {
process.env.WEBINY_MAILER_PASSWORD_SECRET = "really secret secret";

const fullCtx = await handle();

await fullCtx.mailer.createSettings({
input
});

const { handle: noAccessHandle } = createContextHandler({
permissions: []
});

const context = await noAccessHandle();

const response = await context.mailer.getSettings();

expect(response).toEqual({
...input,
id: expect.any(String)
});
});

it("should not be possible to create or update settings due to no permissions", async () => {
process.env.WEBINY_MAILER_PASSWORD_SECRET = "really secret secret";

const { handle: noAccessHandle } = createContextHandler({
permissions: []
});

const context = await noAccessHandle();

let createResponse: any = null;
let createError: any = null;

try {
createResponse = await context.mailer.createSettings({
input
});
} catch (ex) {
createError = {
message: ex.message,
code: ex.code,
data: ex.data
};
}

expect(createResponse).toEqual(null);
expect(createError).toEqual({
message: "Not authorized!",
code: "SECURITY_NOT_AUTHORIZED",
data: {
reason: "Not allowed to update the mailer settings."
}
});

let updateResponse: any = null;
let updateError: any = null;

try {
updateResponse = await context.mailer.updateSettings({
input
});
} catch (ex) {
updateError = {
message: ex.message,
code: ex.code,
data: ex.data
};
}

expect(updateResponse).toEqual(null);
expect(updateError).toEqual({
message: "Not authorized!",
code: "SECURITY_NOT_AUTHORIZED",
data: {
reason: "Not allowed to update the mailer settings."
}
});
});
});
32 changes: 32 additions & 0 deletions packages/api-mailer/__tests__/settings.graphql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,36 @@ describe("Mailer Settings GraphQL", () => {
}
});
});

it("should not have access to saving settings", async () => {
const noAccessHandler = createGraphQLHandler({
permissions: []
});
const [response] = await noAccessHandler.saveSettings({
data: {
host: "dummy-host.webiny",
user: "user",
password: "password",
from: "from@dummy-host.webiny",
replyTo: "replyTo@dummy-host.webiny"
}
});

expect(response).toEqual({
data: {
mailer: {
saveSettings: {
data: null,
error: {
code: "SECURITY_NOT_AUTHORIZED",
data: {
reason: "Not allowed to update the mailer settings."
},
message: "Not authorized!"
}
}
}
}
});
});
});
51 changes: 44 additions & 7 deletions packages/api-mailer/src/crud/settings.crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { getSecret } from "~/crud/settings/secret";
import { createValidation, updateValidation } from "~/crud/settings/validation";
import { CmsEntry, CmsModel } from "@webiny/api-headless-cms/types";
import { attachPasswordObfuscatingHooks } from "~/crud/settings/hooks";
import { NotAuthorizedError } from "@webiny/api-security";

/**
* Note that settings cannot be used if there is no secret defined.
Expand All @@ -33,25 +34,45 @@ export const createSettingsCrud = async (
*/
attachPasswordObfuscatingHooks(context);

const getTenant = () => {
return context.tenancy.getCurrentTenant().id;
};

const validateAccess = async () => {
const permission = await context.security.getPermission("mailer.settings");

if (permission) {
return;
}
throw new NotAuthorizedError({
data: {
reason: `Not allowed to update the mailer settings.`
}
});
};

let secret: string | null = null;
try {
secret = getSecret();
} catch (ex) {}

const getModel = async (): Promise<CmsModel> => {
const model = await context.cms.getModel(SETTINGS_MODEL_ID);
if (model) {
return model;
try {
context.security.disableAuthorization();
const model = await context.cms.getModel(SETTINGS_MODEL_ID);
if (model) {
return model;
}
} catch (ex) {
throw new WebinyError(ex.message, ex.code, ex.data);
} finally {
context.security.enableAuthorization();
}
throw new WebinyError(`Missing CMS Model "${SETTINGS_MODEL_ID}".`, "CMS_MODEL_MISSING", {
modelId: SETTINGS_MODEL_ID
});
};

const getTenant = () => {
return context.tenancy.getCurrentTenant().id;
};

// get
const onSettingsBeforeGet = createTopic<OnSettingsBeforeGetTopicParams>(
"mailer.onSettingsBeforeGet"
Expand Down Expand Up @@ -105,10 +126,12 @@ export const createSettingsCrud = async (
onSettingsUpdateError,
getSettings: async () => {
checkSecret();

const model = await getModel();

const tenant = getTenant();
try {
context.security.disableAuthorization();
await onSettingsBeforeGet.publish({
tenant
});
Expand Down Expand Up @@ -145,6 +168,8 @@ export const createSettingsCrud = async (
tenant,
error: ex
});
} finally {
context.security.enableAuthorization();
}
return null;
},
Expand All @@ -154,6 +179,8 @@ export const createSettingsCrud = async (
*/
async createSettings(this: MailerContextObject, params) {
checkSecret();
await validateAccess();

const { input } = params;

const model = await getModel();
Expand All @@ -175,6 +202,8 @@ export const createSettingsCrud = async (
};

try {
context.security.disableAuthorization();

await onSettingsBeforeCreate.publish({
settings: passwordlessSettings
});
Expand All @@ -200,6 +229,8 @@ export const createSettingsCrud = async (
error: ex
});
throw new WebinyError(ex.message, ex.code, ex.data);
} finally {
context.security.enableAuthorization();
}
},
/**
Expand All @@ -208,6 +239,8 @@ export const createSettingsCrud = async (
*/
async updateSettings(this: MailerContextObject, params) {
checkSecret();
await validateAccess();

const { input, original: initialOriginal } = params;

const model = await getModel();
Expand Down Expand Up @@ -238,6 +271,8 @@ export const createSettingsCrud = async (
password: ""
};
try {
context.security.disableAuthorization();

await onSettingsBeforeUpdate.publish({
settings: passwordlessSettings,
original
Expand Down Expand Up @@ -267,6 +302,8 @@ export const createSettingsCrud = async (
error: ex
});
throw new WebinyError(ex.message, ex.code, ex.data);
} finally {
context.security.enableAuthorization();
}
},
async saveSettings(this: MailerContextObject, params) {
Expand Down
30 changes: 14 additions & 16 deletions packages/app-mailer/src/plugins/Module.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,20 @@ export const Module: React.FC = () => {
/>
</Menu>
</Menu>
{
<AddRoute
exact
path={"/mailer/settings"}
render={() => (
<SecureRoute permission={"mailer.settings"}>
<AdminLayout>
<Helmet title={"Mailer - Settings"} />
<Loader>
<Settings />
</Loader>
</AdminLayout>
</SecureRoute>
)}
/>
}
<AddRoute
exact
path={"/mailer/settings"}
render={() => (
<SecureRoute permission={"mailer.settings"}>
<AdminLayout>
<Helmet title={"Mailer - Settings"} />
<Loader>
<Settings />
</Loader>
</AdminLayout>
</SecureRoute>
)}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,13 @@ declare namespace NodeJS {
APW_SCHEDULER_SCHEDULE_ACTION_HANDLER?: string;
ELASTIC_SEARCH_ENDPOINT?: string;
EVENT_BUS?: string;
/**
* api-mailer
*/
WEBINY_MAILER_HOST?: string;
WEBINY_MAILER_USER?: string;
WEBINY_MAILER_PASSWORD?: string;
WEBINY_MAILER_REPLY_TO?: string;
WEBINY_MAILER_FROM?: string;
}
}
8 changes: 8 additions & 0 deletions typings/env/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,13 @@ declare namespace NodeJS {
STAGED_ROLLOUTS_VARIANT?: string;
ELASTIC_SEARCH_ENDPOINT?: string;
EVENT_BUS?: string;
/**
* api-mailer
*/
WEBINY_MAILER_HOST?: string;
WEBINY_MAILER_USER?: string;
WEBINY_MAILER_PASSWORD?: string;
WEBINY_MAILER_REPLY_TO?: string;
WEBINY_MAILER_FROM?: string;
}
}

0 comments on commit 57df8ee

Please sign in to comment.