Skip to content

Commit

Permalink
Report missing applications in Manage
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Jan 9, 2024
1 parent 96b401f commit d4a69d9
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 12 deletions.
4 changes: 4 additions & 0 deletions client/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -214,3 +214,7 @@ export function validate(type, value) {
export function cron() {
return fetchJson("/api/v1/system/cron")
}

export function rolesUnknownInManage() {
return fetchJson("/api/v1/system/unknown-roles")
}
8 changes: 7 additions & 1 deletion client/src/locale/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ const en = {
guestRoles: "Guests with this role",
cron: "Cron",
invite: "Invite",
tokens: "API tokens"
tokens: "API tokens",
unknownRoles: "Missing applications"
},
home: {
access: "SURFconext Invite",
Expand Down Expand Up @@ -156,6 +157,7 @@ const en = {
deleteConfirmation: "Are you sure you want to delete this role?",
createFlash: "Role {{name}} has been created",
updateFlash: "Role {{name}} has been updated",
unknownInManage: "Unknown in Manage"
},
applications: {
searchPlaceHolder: "Search for roles"
Expand Down Expand Up @@ -425,6 +427,10 @@ const en = {
trigger: "Trigger",
clear: "Clear",
cronInfo: "Trigger the cron job to cleanup resources like expired user-roles, orphaned users and in-active users"
},
unknownRoles: {
title: "Roles linked to applications unknown in Manage",
searchPlaceHolder: "Search..."
}
}

Expand Down
1 change: 1 addition & 0 deletions client/src/locale/nl.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const nl = {
deleteConfirmation: "Weet je zeker dat je deze rol wil verwijderen?",
createFlash: "Rol {{name}} is aangemaakt",
updateFlash: "Rol {{name}} is bijgewerkt",
unknownInManage: "Onbekend in Manage"
},
applications: {
searchPlaceHolder: "Zoek rollen"
Expand Down
10 changes: 8 additions & 2 deletions client/src/pages/Role.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {useNavigate, useParams} from "react-router-dom";
import {useAppStore} from "../stores/AppStore";
import {UnitHeader} from "../components/UnitHeader";
import {ReactComponent as UserLogo} from "@surfnet/sds/icons/functional-icons/id-2.svg";
import {ReactComponent as AlertLogo} from "@surfnet/sds/icons/functional-icons/alert-circle.svg";
import {ReactComponent as WebsiteIcon} from "../icons/network-information.svg";
import {ReactComponent as PersonIcon} from "../icons/persons.svg";
import {ReactComponent as GuestLogo} from "@surfnet/sds/icons/illustrative-icons/hr.svg";
Expand Down Expand Up @@ -159,15 +160,20 @@ export const Role = () => {
}))
}}/>
</div>
{!role.unknownInManage &&
<div className={"meta-data-row"}>
<WebsiteIcon/>
<a href={role.applicationUsages[0].landingPage}
rel="noreferrer"
target="_blank">
<span className={"application-name"}>{`${role.applicationNames}`}</span>
</a>{role.applicationOrganizationName && <span>{` (${role.applicationOrganizationName})`}</span>}
</div>

</div>}
{role.unknownInManage &&
<div className="meta-data-row unknown-in-manage">
<AlertLogo/>
<span className="unknown-in-manage">{I18n.t("roles.unknownInManage")}</span>
</div>}
</div>
</UnitHeader>}
<div className="mod-role">
Expand Down
6 changes: 6 additions & 0 deletions client/src/pages/Role.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,14 @@
height: auto;
margin-right: 15px;
}

&.unknown-in-manage {
color: var(--sds--color--red--500);
font-weight: 600;
}
}
}

.application-name {
margin-right: 5px;
}
Expand Down
8 changes: 8 additions & 0 deletions client/src/pages/System.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import {Loader} from "@surfnet/sds";
import {useNavigate, useParams} from "react-router-dom";
import {useAppStore} from "../stores/AppStore";
import {ReactComponent as CronLogo} from "@surfnet/sds/icons/illustrative-icons/database-check.svg";
import {ReactComponent as RoleLogo} from "@surfnet/sds/icons/illustrative-icons/hierarchy-2.svg";
import Tabs from "../components/Tabs";
import {Page} from "../components/Page";
import {Cron} from "../tabs/Cron";
import {RolesUnknownInManage} from "../tabs/RolesUnknownInManage";
import {Invitations} from "../tabs/Invitations";
import {ReactComponent as InvitationLogo} from "@surfnet/sds/icons/functional-icons/id-1.svg";

Expand Down Expand Up @@ -39,6 +41,12 @@ export const System = () => {
label={I18n.t("tabs.invitations")}
Icon={InvitationLogo}>
<Invitations standAlone={true}/>
</Page>,
<Page key="unknownRoles"
name="unknownRoles"
label={I18n.t("tabs.unknownRoles")}
Icon={RoleLogo}>
<RolesUnknownInManage/>
</Page>

];
Expand Down
6 changes: 4 additions & 2 deletions client/src/tabs/Roles.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import debounce from "lodash.debounce";
import {chipTypeForUserRole} from "../utils/Authority";
import {ReactComponent as VoidImage} from "../icons/undraw_void_-3-ggu.svg";
import Select from "react-select";
import {ReactComponent as AlertLogo} from "@surfnet/sds/icons/functional-icons/alert-circle.svg";

const allValue = "all";

Expand Down Expand Up @@ -158,14 +159,15 @@ export const Roles = () => {
key: "logo",

header: "",
mapper: role => <div className="role-icon">
mapper: role => role.unknownInManage ? <div className="role-icon unknown-in-manage"><AlertLogo/></div> : <div className="role-icon">
{typeof role.logo === "string" ? <img src={role.logo} alt="logo"/> : role.logo}
</div>
},
{
key: "applicationName",
header: I18n.t("roles.applicationName"),
mapper: role => <span>{role.applicationName}</span>
mapper: role => role.unknownInManage ? <span className="unknown-in-manage">{I18n.t("roles.unknownInManage")}</span> :
<span>{role.applicationName}</span>
},
{
key: "name",
Expand Down
9 changes: 9 additions & 0 deletions client/src/tabs/Roles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ div.mod-roles {
td.authority, td.defaultExpiryDays, td.userRoleCount {
text-align: center;
}

.unknown-in-manage {
color: var(--sds--color--red--500);
font-weight: 600;
svg {
margin-left: 15px;
}
}

}
}

Expand Down
105 changes: 105 additions & 0 deletions client/src/tabs/RolesUnknownInManage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import "./RolesUnknownInManage.scss";
import {useAppStore} from "../stores/AppStore";
import React, {useEffect, useState} from "react";
import {Entities} from "../components/Entities";
import I18n from "../locale/I18n";
import {Chip, Loader} from "@surfnet/sds";
import {useNavigate} from "react-router-dom";
import {AUTHORITIES, isUserAllowed} from "../utils/UserRole";
import {rolesUnknownInManage} from "../api";
import {stopEvent} from "../utils/Utils";
import {chipTypeForUserRole} from "../utils/Authority";
import {ReactComponent as AlertLogo} from "@surfnet/sds/icons/functional-icons/alert-circle.svg";
import {deriveApplicationAttributes} from "../utils/Manage";

export const RolesUnknownInManage = () => {
const navigate = useNavigate();
const user = useAppStore(state => state.user);

const [loading, setLoading] = useState(true);
const [roles, setRoles] = useState([]);

useEffect(() => {
if (isUserAllowed(AUTHORITIES.SUPER_USER, user)) {
rolesUnknownInManage()
.then(res => {
deriveApplicationAttributes(res, I18n.locale, I18n.t("roles.multiple"), I18n.t("forms.and"))
setRoles(res);
setLoading(false);
})
} else {
navigate("/404")
}
}, [user]);// eslint-disable-line react-hooks/exhaustive-deps


const openRole = (e, role) => {
const path = `/roles/${role.id}`
if (e.metaKey || e.ctrlKey) {
window.open(path, '_blank');
} else {
stopEvent(e);
navigate(path);
}
};

const columns = [
{
nonSortable: true,
key: "logo",
header: "",
mapper: () => <div className="role-icon unknown-in-manage"><AlertLogo/></div>
},
{
key: "applicationName",
header: I18n.t("roles.applicationName"),
mapper: role => <span className="unknown-in-manage">{I18n.t("roles.unknownInManage")}</span>
},
{
key: "name",
header: I18n.t("roles.accessRole"),
mapper: role => <span>{role.name}</span>
},
{
key: "description",
header: I18n.t("roles.description"),
mapper: role => <span className={"cut-of-lines"}>{role.description}</span>
},
{
key: "authority",
header: I18n.t("roles.authority"),
mapper: role => <Chip type={chipTypeForUserRole(role.authority)}
label={role.isUserRole ? I18n.t(`access.${role.authority}`) :
I18n.t("roles.noMember")}/>
},
{
key: "userRoleCount",
header: I18n.t("roles.userRoleCount"),
mapper: role => role.userRoleCount
}

];

if (loading) {
return <Loader/>
}

return (
<div className="mod-unknown-roles">
<Entities
entities={roles}
modelName="unknownRoles"
showNew={false}
defaultSort="name"
columns={columns}
searchAttributes={["name", "description", "applicationName"]}
customNoEntities={I18n.t(`system.noRoles`)}
loading={false}
inputFocus={true}
hideTitle={false}
rowLinkMapper={openRole}
rowClassNameResolver={entity => (entity.applications || []).length > 1 ? "multi-role" : ""}/>
</div>
);

}
50 changes: 50 additions & 0 deletions client/src/tabs/RolesUnknownInManage.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@import "../styles/vars.scss";

div.mod-unknown-roles {

table.unknown-roles {

thead {
th {
&.applicationName {
width: 15%;
}

&.name {
width: 15%;
}

&.description {
width: 25%;
}

&.authority {
width: 20%;
text-align: center;
}

&.userRoleCount {
width: 15%;
text-align: center;
}

}
}

tbody {
td.authority, td.defaultExpiryDays, td.userRoleCount {
text-align: center;
}

.unknown-in-manage {
color: var(--sds--color--red--500);
font-weight: 600;
svg {
margin-left: 15px;
}
}

}
}

}
13 changes: 10 additions & 3 deletions client/src/utils/Manage.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,19 @@ export const deriveApplicationAttributes = (role, locale, multiple, separator) =
const applications = role.applicationMaps;
if (!isEmpty(applications)) {
if (applications.length === 1) {
role.applicationName = applications[0][`name:${locale}`] || applications[0]["name:en"];
const firstApplication = applications[0];
if (firstApplication.unknown) {
role.unknownInManage = true;
}
role.applicationName = firstApplication[`name:${locale}`] || firstApplication["name:en"];
role.applicationNames = role.applicationName;
role.applicationOrganizationName = applications[0][`OrganizationName:${locale}`] || applications[0]["OrganizationName:en"];
role.logo = applications[0].logo;
role.applicationOrganizationName = firstApplication[`OrganizationName:${locale}`] || firstApplication["OrganizationName:en"];
role.logo = firstApplication.logo;
} else {
role.applicationName = multiple;
if (applications.every(app => app.unknown)) {
role.unknownInManage = true;
}
const appNames = new Set(applications
.map(app => app[`name:${locale}`] || app["name:en"]));
role.applicationNames = splitListSemantically([...appNames], separator);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import access.provision.scim.GroupURN;
import access.repository.UserRepository;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -26,6 +28,8 @@
@SecurityRequirement(name = ATTRIBUTE_AGGREGATION_SCHEME_NAME)
public class AttributeAggregatorController {

private static final Log LOG = LogFactory.getLog(AttributeAggregatorController.class);

private final UserRepository userRepository;
private final Manage manage;
private final String groupUrnPrefix;
Expand All @@ -42,9 +46,16 @@ public AttributeAggregatorController(UserRepository userRepository,
@PreAuthorize("hasRole('ATTRIBUTE_AGGREGATION')")
public ResponseEntity<List<Map<String, String>>> getGroupMemberships(@PathVariable("unspecified_id") String unspecifiedId,
@RequestParam("SPentityID") String spEntityId) {
Optional<Map<String, Object>> optionalProvider = manage
.providerByEntityID(EntityType.SAML20_SP, spEntityId)
.or(() -> manage.providerByEntityID(EntityType.OIDC10_RP, spEntityId));
Optional<Map<String, Object>> optionalProvider;
try {
optionalProvider = manage
.providerByEntityID(EntityType.SAML20_SP, spEntityId)
.or(() -> manage.providerByEntityID(EntityType.OIDC10_RP, spEntityId));
} catch (RuntimeException e) {
LOG.error("Error in communication with Manage", e);
optionalProvider = Optional.empty();
}

if (optionalProvider.isEmpty()) {
return ResponseEntity.ok(Collections.emptyList());
}
Expand Down
Loading

0 comments on commit d4a69d9

Please sign in to comment.