Skip to content

Commit

Permalink
feat(rulesets): validate API security in oas-operation-security-defin…
Browse files Browse the repository at this point in the history
…ed (#2046)
  • Loading branch information
mkistler authored Mar 3, 2022
1 parent b0e4b7c commit 5540250
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ testRule('oas2-operation-security-defined', [
{
name: 'a correct object (just in body)',
document: {
swagger: '2.0',
securityDefinitions: {
apikey: {},
},
Expand All @@ -23,6 +24,27 @@ testRule('oas2-operation-security-defined', [
errors: [],
},

{
name: 'a correct object (API-level security)',
document: {
swagger: '2.0',
securityDefinitions: {
apikey: {},
},
security: [
{
apikey: [],
},
],
paths: {
'/path': {
get: {},
},
},
},
errors: [],
},

{
name: 'invalid object',
document: {
Expand All @@ -43,7 +65,89 @@ testRule('oas2-operation-security-defined', [
errors: [
{
message: 'Operation "security" values must match a scheme defined in the "securityDefinitions" object.',
path: ['paths', '/path', 'get', 'security', '0'],
path: ['paths', '/path', 'get', 'security', '0', 'apikey'],
severity: DiagnosticSeverity.Warning,
},
],
},

{
name: 'invalid object (API-level security)',
document: {
swagger: '2.0',
securityDefinitions: {},
security: [
{
apikey: [],
},
],
paths: {
'/path': {
get: {},
},
},
},
errors: [
{
message: 'API "security" values must match a scheme defined in the "securityDefinitions" object.',
path: ['security', '0', 'apikey'],
severity: DiagnosticSeverity.Warning,
},
],
},

{
name: 'valid and invalid object',
document: {
swagger: '2.0',
securityDefinitions: {
apikey: {},
},
paths: {
'/path': {
get: {
security: [
{
apikey: [],
basic: [],
},
],
},
},
},
},
errors: [
{
message: 'Operation "security" values must match a scheme defined in the "securityDefinitions" object.',
path: ['paths', '/path', 'get', 'security', '0', 'basic'],
severity: DiagnosticSeverity.Warning,
},
],
},

{
name: 'valid and invalid object (API-level security)',
document: {
swagger: '2.0',
securityDefinitions: {
apikey: {},
},
security: [
{
apikey: [],
basic: [],
},
],
paths: {
'/path': {
get: {},
},
},
},
errors: [
{
message: 'API "security" values must match a scheme defined in the "securityDefinitions" object.',
path: ['security', '0', 'basic'],
severity: DiagnosticSeverity.Warning,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,28 @@ testRule('oas3-operation-security-defined', [
},
errors: [],
},
{
name: 'validate a correct object (API-level security)',
document: {
openapi: '3.0.2',
components: {
securitySchemes: {
apikey: {},
},
security: [
{
apikey: [],
},
],
},
paths: {
'/path': {
get: {},
},
},
},
errors: [],
},

{
name: 'return errors on invalid object',
Expand All @@ -46,7 +68,93 @@ testRule('oas3-operation-security-defined', [
errors: [
{
message: 'Operation "security" values must match a scheme defined in the "components.securitySchemes" object.',
path: ['paths', '/path', 'get', 'security', '0'],
path: ['paths', '/path', 'get', 'security', '0', 'apikey'],
severity: DiagnosticSeverity.Warning,
},
],
},

{
name: 'return errors on invalid object (API-level)',
document: {
openapi: '3.0.2',
components: {},
security: [
{
apikey: [],
},
],
paths: {
'/path': {
get: {},
},
},
},
errors: [
{
message: 'API "security" values must match a scheme defined in the "components.securitySchemes" object.',
path: ['security', '0', 'apikey'],
severity: DiagnosticSeverity.Warning,
},
],
},

{
name: 'return errors on valid and invalid object',
document: {
openapi: '3.0.2',
components: {
securitySchemes: {
apikey: {},
},
},
paths: {
'/path': {
get: {
security: [
{
apikey: [],
basic: [],
},
],
},
},
},
},
errors: [
{
message: 'Operation "security" values must match a scheme defined in the "components.securitySchemes" object.',
path: ['paths', '/path', 'get', 'security', '0', 'basic'],
severity: DiagnosticSeverity.Warning,
},
],
},

{
name: 'valid and invalid object (API-level security)',
document: {
openapi: '3.0.2',
components: {
securitySchemes: {
apikey: {},
},
},
security: [
{
apikey: [],
basic: [],
},
],
paths: {
'/path': {
get: {},
},
},
},
errors: [
{
message: 'API "security" values must match a scheme defined in the "components.securitySchemes" object.',
path: ['security', '0', 'basic'],
severity: DiagnosticSeverity.Warning,
},
],
Expand Down
42 changes: 36 additions & 6 deletions packages/rulesets/src/oas/functions/oasOpSecurityDefined.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ type Options = {
schemesPath: JsonPath;
};

export default createRulesetFunction<{ paths: Record<string, unknown> }, Options>(
export default createRulesetFunction<{ paths: Record<string, unknown>; security: unknown[] }, Options>(
{
input: {
type: 'object',
properties: {
paths: {
type: 'object',
},
security: {
type: 'array',
},
},
},
options: {
Expand All @@ -50,6 +53,29 @@ export default createRulesetFunction<{ paths: Record<string, unknown> }, Options
const schemes = _get(targetVal, schemesPath);
const allDefs = isObject(schemes) ? Object.keys(schemes) : [];

// Check global security requirements

const { security } = targetVal;

if (Array.isArray(security)) {
for (const [index, value] of security.entries()) {
if (!isObject(value)) {
continue;
}

const securityKeys = Object.keys(value);

for (const securityKey of securityKeys) {
if (!allDefs.includes(securityKey)) {
results.push({
message: `API "security" values must match a scheme defined in the "${schemesPath.join('.')}" object.`,
path: ['security', index, securityKey],
});
}
}
}
}

for (const { path, operation, value } of getAllOperations(paths)) {
if (!isObject(value)) continue;

Expand All @@ -66,11 +92,15 @@ export default createRulesetFunction<{ paths: Record<string, unknown> }, Options

const securityKeys = Object.keys(value);

if (securityKeys.length > 0 && !allDefs.includes(securityKeys[0])) {
results.push({
message: 'Operation must not reference an undefined security scheme.',
path: ['paths', path, operation, 'security', index],
});
for (const securityKey of securityKeys) {
if (!allDefs.includes(securityKey)) {
results.push({
message: `Operation "security" values must match a scheme defined in the "${schemesPath.join(
'.',
)}" object.`,
path: ['paths', path, operation, 'security', index, securityKey],
});
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/rulesets/src/oas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ const ruleset = {
},
'oas2-operation-security-defined': {
description: 'Operation "security" values must match a scheme defined in the "securityDefinitions" object.',
message: '{{error}}',
recommended: true,
formats: [oas2],
type: 'validation',
Expand Down Expand Up @@ -590,6 +591,7 @@ const ruleset = {
'oas3-operation-security-defined': {
description:
'Operation "security" values must match a scheme defined in the "components.securitySchemes" object.',
message: '{{error}}',
recommended: true,
formats: [oas3],
type: 'validation',
Expand Down

0 comments on commit 5540250

Please sign in to comment.