Skip to content

Commit

Permalink
Add all scopes to API design
Browse files Browse the repository at this point in the history
  • Loading branch information
jraddaoui committed Jun 24, 2024
1 parent efd336f commit 5b0cf03
Show file tree
Hide file tree
Showing 19 changed files with 489 additions and 146 deletions.
4 changes: 4 additions & 0 deletions dashboard/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ VITE_OIDC_AUTHORITY=\$VITE_OIDC_AUTHORITY
VITE_OIDC_CLIENT_ID=\$VITE_OIDC_CLIENT_ID
VITE_OIDC_REDIRECT_URI=\$VITE_OIDC_REDIRECT_URI
VITE_OIDC_EXTRA_SCOPES=\$VITE_OIDC_EXTRA_SCOPES
VITE_OIDC_ABAC_ENABLED=\$VITE_OIDC_ABAC_ENABLED
VITE_OIDC_CLAIM_PATH=\$VITE_OIDC_CLAIM_PATH
VITE_OIDC_CLAIM_PATH_SEPARATOR=\$VITE_OIDC_CLAIM_PATH_SEPARATOR
VITE_OIDC_CLAIM_VALUE_PREFIX=\$VITE_OIDC_CLAIM_VALUE_PREFIX
4 changes: 4 additions & 0 deletions dashboard/.env.development
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ VITE_OIDC_AUTHORITY=http://keycloak:7470/realms/enduro
VITE_OIDC_CLIENT_ID=enduro
VITE_OIDC_REDIRECT_URI=http://localhost:8080/user/signin-callback
VITE_OIDC_EXTRA_SCOPES=enduro
VITE_OIDC_ABAC_ENABLED=true
VITE_OIDC_CLAIM_PATH=enduro
VITE_OIDC_CLAIM_PATH_SEPARATOR=
VITE_OIDC_CLAIM_VALUE_PREFIX=
4 changes: 4 additions & 0 deletions dashboard/src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ interface ImportMetaEnv {
readonly VITE_OIDC_CLIENT_ID: string;
readonly VITE_OIDC_REDIRECT_URI: string;
readonly VITE_OIDC_EXTRA_SCOPES: string;
readonly VITE_OIDC_ABAC_ENABLED: string;
readonly VITE_OIDC_CLAIM_PATH: string;
readonly VITE_OIDC_CLAIM_PATH_SEPARATOR: string;
readonly VITE_OIDC_CLAIM_VALUE_PREFIX: string;
}

interface ImportMeta {
Expand Down
94 changes: 94 additions & 0 deletions dashboard/src/stores/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,19 @@ type BreadcrumbItem = {
text?: string;
};

type ABACConfig = {
Enabled: boolean;
ClaimPath: string;
ClaimPathSeparator: string;
ClaimValuePrefix: string;
};

export const useLayoutStore = defineStore("layout", {
state: () => ({
sidebarCollapsed: false as boolean,
breadcrumb: [] as Array<BreadcrumbItem>,
user: null as User | null,
attributes: [] as Array<String>,
}),
getters: {
isUserValid(): boolean {
Expand Down Expand Up @@ -43,6 +51,7 @@ export const useLayoutStore = defineStore("layout", {
},
setUser(user: User | null) {
this.user = user;
this.setAttributes();
},
removeUser() {
// TODO: end session upstream.
Expand All @@ -51,5 +60,90 @@ export const useLayoutStore = defineStore("layout", {
router.push({ name: "/" });
});
},
getABACConfig(): ABACConfig {
const env = import.meta.env;
let config: ABACConfig = {
Enabled: false,
ClaimPath: "enduro",
ClaimPathSeparator: "",
ClaimValuePrefix: "",
};

if (env.VITE_OIDC_ABAC_ENABLED != undefined) {
config.Enabled = env.VITE_OIDC_ABAC_ENABLED.toLowerCase() === "true";
}
if (env.VITE_OIDC_CLAIM_PATH != undefined) {
config.ClaimPath = env.VITE_OIDC_CLAIM_PATH;
}
if (env.VITE_OIDC_CLAIM_PATH_SEPARATOR != undefined) {
config.ClaimPathSeparator = env.VITE_OIDC_CLAIM_PATH_SEPARATOR;
}
if (env.VITE_OIDC_CLAIM_VALUE_PREFIX != undefined) {
config.ClaimValuePrefix = env.VITE_OIDC_CLAIM_VALUE_PREFIX;
}

return config;
},
setAttributes() {
const cfg = this.getABACConfig();
console.log(cfg);
if (!cfg.Enabled || !this.user) {
return;
}

let data: Record<string, any>;
try {
data = JSON.parse(
Buffer.from(this.user.access_token, "base64").toString("utf-8"),
);
} catch (err) {
throw new Error(`Error decoding or parsing token: ${err}`);
}

const keys = cfg.ClaimPathSeparator
? cfg.ClaimPath.split(cfg.ClaimPathSeparator)
: [cfg.ClaimPath];

for (let i = 0; i < keys.length; i++) {
const key = keys[i];
const value = data[key];
if (value === undefined) {
throw new Error(
`Attributes not found in token, claim path: ${cfg.ClaimPath}`,
);
}

if (i === keys.length - 1) {
if (!Array.isArray(value)) {
throw new Error(
`Attributes are not part of a multivalue claim, claim path: ${cfg.ClaimPath}`,
);
}

this.attributes = value.reduce((acc: string[], item: any) => {
if (
typeof item === "string" &&
item.startsWith(cfg.ClaimValuePrefix)
) {
acc.push(item.substring(cfg.ClaimValuePrefix.length));
}
return acc;
}, []);
console.log(this.attributes);

return;
}

if (typeof value !== "object" || value === null) {
throw new Error(
`Attributes not found in token, claim path: ${cfg.ClaimPath}`,
);
}

data = value;
}

throw new Error("Unexpected error parsing attributes");
},
},
});
19 changes: 16 additions & 3 deletions internal/api/design/design.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,22 @@ import (

var JWTAuth = JWTSecurity("jwt", func() {
Description("Secures endpoint by requiring a valid JWT token.")
Scope("package:list", "List packages access")
Scope("package:read", "Read packages access")
Scope("storage:download", "Download stored packages access")
Scope("package:list")
Scope("package:listActions")
Scope("package:move")
Scope("package:read")
Scope("package:review")
Scope("package:upload")
Scope("storage:location:create")
Scope("storage:location:list")
Scope("storage:location:listPackages")
Scope("storage:location:read")
Scope("storage:package:create")
Scope("storage:package:download")
Scope("storage:package:move")
Scope("storage:package:read")
Scope("storage:package:review")
Scope("storage:package:submit")
})

var _ = API("enduro", func() {
Expand Down
27 changes: 26 additions & 1 deletion internal/api/design/package_.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ var _ = Service("package", func() {
Response("forbidden", StatusForbidden)
})
Method("monitor_request", func() {
Description("Request access to the /monitor WebSocket.")
Description("Request access to the /monitor WebSocket")
// For now, the monitor websocket requires all the scopes from this service.
Security(JWTAuth, func() {
Scope("package:list")
Scope("package:listActions")
Scope("package:move")
Scope("package:read")
Scope("package:review")
Scope("package:upload")
})
Payload(func() {
Token("token", String)
})
Expand All @@ -34,6 +43,7 @@ var _ = Service("package", func() {
})
})
Method("monitor", func() {
Description("Obtain access to the /monitor WebSocket")
// Disable JWTAuth security (it validates the previous method cookie).
NoSecurity()
Payload(func() {
Expand Down Expand Up @@ -106,6 +116,9 @@ var _ = Service("package", func() {
})
Method("preservation_actions", func() {
Description("List all preservation actions by ID")
Security(JWTAuth, func() {
Scope("package:listActions")
})
Payload(func() {
Attribute("id", UInt, "Identifier of package to look up")
Token("token", String)
Expand All @@ -121,6 +134,9 @@ var _ = Service("package", func() {
})
Method("confirm", func() {
Description("Signal the package has been reviewed and accepted")
Security(JWTAuth, func() {
Scope("package:review")
})
Payload(func() {
Attribute("id", UInt, "Identifier of package to look up")
TypedAttributeUUID("location_id", "Identifier of storage location")
Expand All @@ -140,6 +156,9 @@ var _ = Service("package", func() {
})
Method("reject", func() {
Description("Signal the package has been reviewed and rejected")
Security(JWTAuth, func() {
Scope("package:review")
})
Payload(func() {
Attribute("id", UInt, "Identifier of package to look up")
Token("token", String)
Expand All @@ -158,6 +177,9 @@ var _ = Service("package", func() {
})
Method("move", func() {
Description("Move a package to a permanent storage location")
Security(JWTAuth, func() {
Scope("package:move")
})
Payload(func() {
Attribute("id", UInt, "Identifier of package to move")
TypedAttributeUUID("location_id", "Identifier of storage location")
Expand All @@ -177,6 +199,9 @@ var _ = Service("package", func() {
})
Method("move_status", func() {
Description("Retrieve the status of a permanent storage location move of the package")
Security(JWTAuth, func() {
Scope("package:move")
})
Payload(func() {
Attribute("id", UInt, "Identifier of package to move")
Token("token", String)
Expand Down
39 changes: 36 additions & 3 deletions internal/api/design/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ var _ = Service("storage", func() {
})
Method("submit", func() {
Description("Start the submission of a package")
Security(JWTAuth, func() {
Scope("storage:package:submit")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Attribute("name", String)
Expand All @@ -36,6 +39,9 @@ var _ = Service("storage", func() {
})
Method("create", func() {
Description("Create a new package")
Security(JWTAuth, func() {
Scope("storage:package:create")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Attribute("name", String, "Name of the package")
Expand All @@ -57,7 +63,10 @@ var _ = Service("storage", func() {
})
})
Method("update", func() {
Description("Signal the storage service that an upload is complete")
Description("Signal that a package submission is complete")
Security(JWTAuth, func() {
Scope("storage:package:submit")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Token("token", String)
Expand All @@ -76,7 +85,7 @@ var _ = Service("storage", func() {
Method("download", func() {
Description("Download package by AIPID")
Security(JWTAuth, func() {
Scope("storage:download")
Scope("storage:package:download")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Expand All @@ -93,6 +102,9 @@ var _ = Service("storage", func() {
})
Method("locations", func() {
Description("List locations")
Security(JWTAuth, func() {
Scope("storage:location:list")
})
Payload(func() {
Token("token", String)
})
Expand All @@ -103,7 +115,10 @@ var _ = Service("storage", func() {
})
})
Method("add_location", func() {
Description("Add a storage location")
Description("Create a storage location")
Security(JWTAuth, func() {
Scope("storage:location:create")
})
Payload(func() {
Attribute("name", String)
Attribute("description", String)
Expand Down Expand Up @@ -132,6 +147,9 @@ var _ = Service("storage", func() {
})
Method("move", func() {
Description("Move a package to a permanent storage location")
Security(JWTAuth, func() {
Scope("storage:package:move")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
TypedAttributeUUID("location_id", "Identifier of storage location")
Expand All @@ -151,6 +169,9 @@ var _ = Service("storage", func() {
})
Method("move_status", func() {
Description("Retrieve the status of a permanent storage location move of the package")
Security(JWTAuth, func() {
Scope("storage:package:move")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Token("token", String)
Expand All @@ -168,6 +189,9 @@ var _ = Service("storage", func() {
})
Method("reject", func() {
Description("Reject a package")
Security(JWTAuth, func() {
Scope("storage:package:review")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Token("token", String)
Expand All @@ -186,6 +210,9 @@ var _ = Service("storage", func() {
})
Method("show", func() {
Description("Show package by AIPID")
Security(JWTAuth, func() {
Scope("storage:package:read")
})
Payload(func() {
AttributeUUID("aip_id", "Identifier of AIP")
Token("token", String)
Expand All @@ -201,6 +228,9 @@ var _ = Service("storage", func() {
})
Method("show_location", func() {
Description("Show location by UUID")
Security(JWTAuth, func() {
Scope("storage:location:read")
})
Payload(func() {
// TODO: explore how we can use uuid.UUID that are also URL params.
AttributeUUID("uuid", "Identifier of location")
Expand All @@ -217,6 +247,9 @@ var _ = Service("storage", func() {
})
Method("location_packages", func() {
Description("List all the packages stored in the location with UUID")
Security(JWTAuth, func() {
Scope("storage:location:listPackages")
})
Payload(func() {
// TODO: explore how we can use uuid.UUID that are also URL params.
AttributeUUID("uuid", "Identifier of location")
Expand Down
4 changes: 4 additions & 0 deletions internal/api/design/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ var _ = Service("upload", func() {
Response("forbidden", StatusForbidden)
})
Method("upload", func() {
Description("Upload a package to trigger an ingest workflow")
Security(JWTAuth, func() {
Scope("package:upload")
})
Payload(func() {
Attribute("content_type", String, "Content-Type header, must define value for multipart boundary.", func() {
Default("multipart/form-data; boundary=goa")
Expand Down
Loading

0 comments on commit 5b0cf03

Please sign in to comment.