Skip to content

Commit f0a67cb

Browse files
committed
Fixes #411
1 parent c8e5f48 commit f0a67cb

13 files changed

Lines changed: 123 additions & 34 deletions

File tree

client/src/components/ContactPersons.jsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const ContactPersons = ({
1717
setFocusedId,
1818
inputRef,
1919
initial,
20+
readOnly = false
2021
}) => {
2122

2223

@@ -53,7 +54,8 @@ export const ContactPersons = ({
5354
<section className="inner-right">
5455
<h3>{I18n.t("connection.contacts.label")}</h3>
5556
<p>{I18n.t("connection.contacts.info", {
56-
example: emailPlaceholder("support", application?.organization?.name || application.name, I18n.t("forms.or"))})}</p>
57+
example: emailPlaceholder("support", application?.organization?.name || application.name, I18n.t("forms.or"))
58+
})}</p>
5759
{Object.keys(contactPersonsGrouped).map((contactType, index) =>
5860
<section key={index} className="contact-person-section">
5961
<h4>{I18n.t(`connection.contacts.${contactType}`)}</h4>
@@ -68,9 +70,10 @@ export const ContactPersons = ({
6870
I18n.t(`connection.contacts.${contactPerson.type}Placeholder`), application?.organization?.name || application.name,
6971
I18n.t("forms.or")
7072
)}
73+
disabled={readOnly}
7174
onChange={e => updateContactPerson(contactPerson.id, e)}
7275
onRef={el => contactPerson.id === focusedId && (inputRef.current = el)}
73-
button={(contactPerson.type === contactPersonTypes.technical && innerIndex > 0) ?
76+
button={(contactPerson.type === contactPersonTypes.technical && innerIndex > 0 && !readOnly) ?
7477
<Button onClick={() => removeContactPerson(contactPerson.id)}
7578
type={ButtonType.Delete}/> : null}
7679
/>

client/src/locale/en.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,8 +1000,14 @@ const en = {
10001000
delete: "Delete",
10011001
deleteConfirmation: "Are you sure you want to remove your account? This action is not reversible.",
10021002
attributes: "Attributes",
1003-
organization: "Organization(s)",
1004-
1003+
organization: "Organization membership",
1004+
application: "Application membership",
1005+
organizationMultiple: "Organization memberships",
1006+
applicationMultiple: "Application memberships",
1007+
externalUser: "External user",
1008+
externalUserTooltip: "An external user - as opposed to an internal user - is a user who only has an eduID account and not an institutional account.",
1009+
internalUser: "Internal user",
1010+
internalUserTooltip: "An internal user - as opposed to an external user - is a user who has logged in with an institutional account."
10051011
}
10061012
}
10071013

client/src/organization/UserManagement.jsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import I18n from "../locale/I18n";
55
import {Loader} from "@surfnet/sds";
66
import {useNavigate, useParams} from "react-router-dom";
77
import {organizationUserManagementById} from "../api/index.js";
8-
import {convertServerApplicationToClient} from "../utils/Application.js";
98
import {TeamManagement} from "./TeamManagement.jsx";
109
import {currentUserMembershipAuthority} from "../utils/Permissions.js";
1110
import {JoinRequestManagement} from "./JoinRequestManagement.jsx";
@@ -35,7 +34,6 @@ export const UserManagement = () => {
3534
} else {
3635
organizationUserManagementById(organizationId)
3736
.then(res => {
38-
res.applications = res.applications.map(application => convertServerApplicationToClient(application))
3937
setOrganization(res);
4038
useAppStore.setState({
4139
currentOrganization: res,

client/src/pages/MyOrganization.jsx

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import "./MyOrganization.scss";
1212
import I18n from "../locale/I18n";
1313
import ConfirmationDialog from "../components/ConfirmationDialog.jsx";
1414
import DOMPurify from "dompurify";
15-
import {isOrganizationAdmin} from "../utils/Permissions.js";
15+
import {authorities, isOrganizationAdmin} from "../utils/Permissions.js";
1616
import {Button, ButtonType, Loader} from "@surfnet/sds";
1717
import {ContactPersons} from "../components/ContactPersons.jsx";
1818
import {contactSectionValid, convertServerApplicationToClient} from "../utils/Application.js";
@@ -46,6 +46,11 @@ const MyOrganization = ({refreshUser}) => {
4646

4747
const navigate = useNavigate();
4848

49+
const adminUser = useMemo(() => {
50+
return user.superUser || user.organizationMemberships
51+
.some(om => om.authority === authorities.ADMIN && om.organization.id === organizationId);
52+
}, [user, organizationId]);
53+
4954
useEffect(() => {
5055
if (isEmpty(organizationId)) {
5156
navigate("/home");
@@ -114,7 +119,8 @@ const MyOrganization = ({refreshUser}) => {
114119
setFocusedId={setFocusedId}
115120
focusedId={focusedId}
116121
inputRef={inputRef}
117-
initial={initial}/>
122+
initial={initial}
123+
readOnly={!adminUser}/>
118124
}
119125

120126
const changeKeyWords = options => {
@@ -141,6 +147,7 @@ const MyOrganization = ({refreshUser}) => {
141147
}))}
142148
onChange={changeKeyWords}
143149
isMulti={true}
150+
disabled={!adminUser}
144151
creatable={true}
145152
/>
146153
<p className="info">{I18n.t("myOrganization.keyWordsInfo")}</p>
@@ -154,6 +161,7 @@ const MyOrganization = ({refreshUser}) => {
154161
<h3>{I18n.t("myOrganization.generalInformation")}</h3>
155162
<InputField name={I18n.t("myOrganization.name")}
156163
value={organization.name}
164+
disabled={!adminUser}
157165
onChange={e => setOrganization({...organization, name: e.target.value})}/>
158166
</section>
159167
)
@@ -203,10 +211,8 @@ const MyOrganization = ({refreshUser}) => {
203211
});
204212

205213
}
206-
207214
}
208215

209-
210216
const renderCurrentSection = () => {
211217
switch (section) {
212218
case sections.contactPersons: {
@@ -253,7 +259,7 @@ const MyOrganization = ({refreshUser}) => {
253259
{renderCurrentSection()}
254260
</div>
255261
</div>
256-
{section !== sections.delete &&
262+
{(section !== sections.delete && adminUser) &&
257263
<div className="actions proceed">
258264
<Button onClick={() => externalUser ? saveExternalOrganization() : saveInternalOrganization()}
259265
disabled={!initial && !contactSectionValid(organization) && isEmpty(organization.name)}

client/src/pages/Profile.jsx

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "./Profile.scss";
44
import I18n from "../locale/I18n";
55
import ConfirmationDialog from "../components/ConfirmationDialog.jsx";
66
import DOMPurify from "dompurify";
7-
import {Button, ButtonType} from "@surfnet/sds";
7+
import {Button, ButtonType, Checkbox, Tooltip} from "@surfnet/sds";
88
import {dateFromEpoch} from "../utils/Date.js";
99
import {isEmpty, stopEvent} from "../utils/Utils.js";
1010
import {deleteUser, logout} from "../api/index.js";
@@ -61,6 +61,10 @@ const Profile = ({setIsAuthenticated}) => {
6161
}
6262

6363
const {open, cancel, action, question, okButton} = confirmation;
64+
const applicationMemberships = (user.organizationMemberships || [])
65+
.map(orgMembership => orgMembership.applicationMemberships)
66+
.flat();
67+
const externalUser = user.externalUser;
6468
return (
6569
<div
6670
className="profile-outer-container">
@@ -104,10 +108,49 @@ const Profile = ({setIsAuthenticated}) => {
104108
)}
105109

106110
{!isEmpty(user.organizationMemberships) &&
107-
<InputField name={I18n.t("profile.organization")}
108-
value={user.organizationMemberships.map(m => m.organization.name).join(", ")}
109-
noInput={true}
110-
/>}
111+
<div className="multi-list">
112+
<p>{I18n.t(`profile.organization${user.organizationMemberships.length > 1 ? "Multiple" : ""}`)}</p>
113+
<ul>
114+
{user.organizationMemberships.map((orgMembership, index) =>
115+
<li key={index}>
116+
<span> {`${orgMembership.organization.name}`}
117+
<em> {" - " + I18n.t(`roles.${orgMembership.authority.toLowerCase()}`)}</em>
118+
</span>
119+
</li>
120+
)}
121+
</ul>
122+
</div>}
123+
{!isEmpty(applicationMemberships) &&
124+
<div className="multi-list">
125+
<p>{I18n.t(`profile.application${applicationMemberships.length > 1 ? "Multiple" : ""}`)}</p>
126+
<ul>
127+
{applicationMemberships.map((appMembership, index) =>
128+
<li key={index}>
129+
<span> {`${appMembership.applicationName}`}
130+
<em> {" - " + I18n.t(`roles.${appMembership.authority.toLowerCase()}`)}</em>
131+
</span>
132+
</li>
133+
)}
134+
</ul>
135+
</div>}
136+
{externalUser &&
137+
<div className="multi-list">
138+
<div className="external-user-container">
139+
<p>{I18n.t("profile.externalUser")}</p>
140+
<Tooltip tip={I18n.t("profile.externalUserTooltip")}/>
141+
</div>
142+
<Checkbox value={externalUser}
143+
readOnly={true}/>
144+
</div>}
145+
{!externalUser &&
146+
<div className="multi-list">
147+
<div className="external-user-container">
148+
<p>{I18n.t("profile.internalUser")}</p>
149+
<Tooltip tip={I18n.t("profile.internalUserTooltip")}/>
150+
</div>
151+
<Checkbox value={!externalUser}
152+
readOnly={true}/>
153+
</div>}
111154
</div>
112155
<div className="delete-container">
113156
<Button onClick={e => doDelete(e, true)}

client/src/pages/Profile.scss

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,25 @@ div.profile-outer-container {
2323

2424
}
2525

26+
div.multi-list {
27+
p {
28+
font-weight: 600;
29+
margin: 12px 0 var(--sds--space--1);
30+
}
31+
ul {
32+
list-style: initial;
33+
li {
34+
margin-left: 15px;
35+
}
36+
}
37+
.external-user-container {
38+
display: flex;
39+
}
40+
.sds--checkbox-container {
41+
margin-left: 4px;
42+
}
43+
}
44+
2645
div.user-container {
2746
background-color: white;
2847
display: grid;
@@ -32,7 +51,7 @@ div.profile-outer-container {
3251
grid-template-columns: [first] 1fr [second] 1fr;
3352

3453
h5 {
35-
grid-column: 1 / -1;
54+
grid-column: 1 / -1;
3655
}
3756
}
3857

client/src/utils/Connection.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
//Deliberate design choice to have a different format on the server to have all complex data in the metaData attribute
2+
import {isEmpty} from "./Utils.js";
3+
24
export const convertClientConnectionToServer = (application, connection, arpInfo) => {
35

46
const {motivations, additionalAttributes, profile, profileMotivation} = connection;
@@ -58,7 +60,8 @@ export const convertServerConnectionToClient = (connection, protocolOptions, pro
5860
const motivations = additionalAttributesUrns
5961
.reduce((acc, urn) => {
6062
const attribute = additionalAttributes.find(attr => attr.urn === urn);
61-
acc[attribute.name] = attributes[urn][0].motivation;
63+
const attrMotivation = attributes[urn][0].motivation;
64+
acc[attribute.name] = isEmpty(attrMotivation) ? `Need ${attribute.name}` : attrMotivation;
6265
return acc;
6366
}, {})
6467
const profileOption = profileOptions.find(option => option.value === profile);

client/src/utils/MenuItems.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,24 @@ export const mainMenuItems = {
2525
}
2626

2727
export const menuItemsForUser = user => {
28-
//Every user has access to the help menu items
28+
//Every user has access to the home, catalogue and help menu items
2929
const newMenuItems = [mainMenuItems.home, mainMenuItems.catalogue, mainMenuItems.serviceDesk, mainMenuItems.feedback];
3030
const noOrganizationMemberships = isEmpty(user.organizationMemberships);
3131
if (noOrganizationMemberships) {
3232
return newMenuItems;
3333
}
34-
//Guest with no application membership
35-
newMenuItems.push(mainMenuItems.idp);
36-
const emptyGuests = user.organizationMemberships.every(m => m.autority === authorities.GUEST && isEmpty(m.applicationMemberships));
37-
if (emptyGuests) {
34+
//If there is at least one organizationMembership, then we show yourApps
35+
newMenuItems.push(mainMenuItems.yourApps);
36+
const onlyGuest = user.organizationMemberships.every(m => m.authority === authorities.GUEST);
37+
if (onlyGuest) {
3838
return newMenuItems;
3939
}
40-
newMenuItems.push(mainMenuItems.yourApps, mainMenuItems.users, mainMenuItems.accessibleApps, );
40+
const isMember = user.organizationMemberships.some(m => m.authority === authorities.MEMBER);
41+
if (isMember) {
42+
newMenuItems.push(mainMenuItems.users);
43+
}
4144
if (!user.externalUser) {
42-
newMenuItems.push(mainMenuItems.invite, mainMenuItems.sram);
45+
newMenuItems.push(mainMenuItems.accessibleApps, mainMenuItems.idp, mainMenuItems.invite, mainMenuItems.sram);
4346
}
4447
return newMenuItems;
4548
}

server/src/main/java/access/api/OrganizationController.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,8 @@ public ResponseEntity<Organization> light(@PathVariable("id") Long id) {
203203
@GetMapping("/invitation/{id}")
204204
public ResponseEntity<Organization> name(@PathVariable("id") Long id) {
205205
LOG.debug("/name");
206-
Organization organization = organizationRepository.findById(id)
206+
Organization organization = organizationRepository.findApplicationsOrganizationById(id)
207207
.orElseThrow(() -> new NotFoundException("Organisation not found"));
208-
Hibernate.initialize(organization.getApplications());
209208
return ResponseEntity.ok(organization);
210209
}
211210

server/src/main/java/access/manage/ConnectionProviderConverter.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
import java.util.stream.Collectors;
1212
import java.util.stream.IntStream;
1313

14-
import static access.manage.ManageData.getData;
15-
import static access.manage.ManageData.getMetaDataFields;
14+
import static access.manage.ManageData.*;
1615

1716
@SuppressWarnings("unchecked")
1817
public class ConnectionProviderConverter {
@@ -298,7 +297,7 @@ private void mergeAttributeReleasePolicies(Map<String, Object> connectionMetaDat
298297
}
299298

300299
private void putIf(Map<String, Object> result, String key, Object value) {
301-
if ((value instanceof String && StringUtils.hasText((String) value)) || value != null) {
300+
if (!isEmpty(value)) {
302301
result.put(key, value);
303302
}
304303
}

0 commit comments

Comments
 (0)