Skip to content

Commit 5f7d458

Browse files
committed
WIP for user permissions
1 parent fb41115 commit 5f7d458

File tree

12 files changed

+92
-49
lines changed

12 files changed

+92
-49
lines changed

client/src/connection/Testing.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,8 +789,10 @@ export const Testing = ({
789789
{iDps.map((idp, index) =>
790790
<div key={index} className="idp">
791791
<Checkbox name={idp.name}
792+
readOnly={true}
792793
value={allowedEntities.includes(idp.entityid)}
793-
onChange={e => changeAllowedTestEntity(idp, e)}/>
794+
//onChange={e => changeAllowedTestEntity(idp, e)}
795+
/>
794796
<div className="idp-info">
795797
<p dangerouslySetInnerHTML={{__html: idp.name}}/>
796798
<p dangerouslySetInnerHTML={{__html: idp[`description${I18n.locale.toUpperCase()}`]}}/>

client/src/locale/en.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,7 @@ const en = {
460460
value: "Value"
461461
},
462462
testIdPs: {
463-
info: "Kies met welke IdP’s je wilt testen of het federatief inloggen werkt.",
463+
info: "Met de volgende IdP’s kan je testen of het federatief inloggen werkt.",
464464
subTitle: "Test-IdP’s van SURF",
465465
institutionIdPs: "Test-IdP’s van instellingen",
466466
institutionIdPsInfo: "Je kunt ook testen met accounts en data van instellingen. <strong>Let wel op<strong/>: Je moet zelf contact opnemen voor de test-inloggegevens voor hun test-IdP’s.",

client/src/pages/InvitationForm.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {isEmpty} from "../utils/Utils";
1010
import ErrorIndicator from "../components/ErrorIndicator";
1111
import SelectField from "../components/SelectField";
1212
import EmailField from "../components/EmailField";
13-
import {allAuthorities, authorities, currentUserMembershipAuthority} from "../utils/Permissions.js";
13+
import {allAuthorities, authorities, authorityWeights, currentUserMembershipAuthority} from "../utils/Permissions.js";
1414
import {TabHeader} from "../components/TabHeader.jsx";
1515
import {mainMenuItems} from "../utils/MenuItems.js";
1616

@@ -44,7 +44,7 @@ export const InvitationForm = () => {
4444
useAppStore.setState({
4545
breadcrumbPaths: [
4646
{path: "/home", value: I18n.t("breadCrumb.access"), menuItemName: mainMenuItems.home},
47-
{path: `/organization/${organizationId}`, value: res.name, menuItemName: mainMenuItems.yourApps},
47+
{path: `/users/${organizationId}/team`, value: I18n.t("navigation.users"), menuItemName: mainMenuItems.users},
4848
{value: I18n.t("breadCrumb.invitations")}
4949
]
5050
});
@@ -126,7 +126,7 @@ export const InvitationForm = () => {
126126
{(!initial && isEmpty(invitation.invites)) &&
127127
<ErrorIndicator msg={I18n.t("invitation.requiredEmail")}/>}
128128

129-
{authorityOptions.length > 1 &&
129+
{authorityOptions.length > 0 &&
130130
<SelectField
131131
value={authorityOptions.find(option => option.value === invitation.intendedAuthority)
132132
|| authorityOptions[authorityOptions.length - 1]}
@@ -182,8 +182,9 @@ export const InvitationForm = () => {
182182
const renderForm = () => {
183183
const disabledSubmit = !initial && !isValid();
184184
const authorityOptions = allAuthorities
185-
.filter(authority => currentUserAuthority === authorities.ADMIN || authority !== authorities.ADMIN)
186-
.map(authority => ({value: authority, label: I18n.t(`roles.${authority.toLowerCase()}`)}))
185+
.filter(authority => currentUserAuthority === authorities.ADMIN ||
186+
authorityWeights[currentUserAuthority] > authorityWeights[authority])
187+
.map(authority => ({value: authority, label: I18n.t(`roles.${authority.toLowerCase()}`)}));
187188
return (
188189
<>
189190
{renderFormElements(authorityOptions)}

client/src/pages/Organizations.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ export const Organizations = ({pendingApproval, tab}) => {
227227
tip={I18n.t("tooltips.organizationsIcon",
228228
{
229229
name: org.name,
230-
createdAt: dateFromEpoch(org.created_at, false),
230+
createdAt: dateFromEpoch(org.created_at || org.createdAt, false),
231231
status: I18n.t(`organizations.${org.status.toLowerCase()}`)
232232
})}/>
233233
</div>

client/src/utils/MenuItems.js

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import ConnectedIcon from "@surfnet/sds/icons/illustrative-icons/connected.svg";
99
import TeamIcon from "@surfnet/sds/icons/illustrative-icons/team.svg";
1010
import HeadPhonesIcon from "@surfnet/sds/icons/illustrative-icons/headphones.svg";
1111
import FeedbackIcon from "@surfnet/sds/icons/illustrative-icons/feedback.svg";
12+
import {authorities} from "./Permissions.js";
1213

1314
export const mainMenuItems = {
1415
home: "home",
@@ -24,16 +25,22 @@ export const mainMenuItems = {
2425
}
2526

2627
export const menuItemsForUser = user => {
27-
const hasOrganizationMemberships = !isEmpty(user.organizationMemberships);
28-
const newMenuItems = [mainMenuItems.home, mainMenuItems.catalogue];
29-
if (hasOrganizationMemberships) {
30-
newMenuItems.push(mainMenuItems.yourApps, mainMenuItems.users, mainMenuItems.idp);
28+
//Every user has access to the help menu items
29+
const newMenuItems = [mainMenuItems.home, mainMenuItems.catalogue, mainMenuItems.serviceDesk, mainMenuItems.feedback];
30+
const noOrganizationMemberships = isEmpty(user.organizationMemberships);
31+
if (noOrganizationMemberships) {
32+
return newMenuItems;
33+
}
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) {
38+
return newMenuItems;
3139
}
40+
newMenuItems.push(mainMenuItems.yourApps, mainMenuItems.users, mainMenuItems.accessibleApps, );
3241
if (!user.externalUser) {
33-
newMenuItems.push(mainMenuItems.accessibleApps, mainMenuItems.invite, mainMenuItems.sram);
42+
newMenuItems.push(mainMenuItems.invite, mainMenuItems.sram);
3443
}
35-
//Every user has access to the help menu items
36-
newMenuItems.push(mainMenuItems.serviceDesk, mainMenuItems.feedback);
3744
return newMenuItems;
3845
}
3946

client/src/utils/Permissions.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ export const authorities = {
77
GUEST: "GUEST"
88
}
99

10+
export const authorityWeights = {
11+
[authorities.SUPER_USER]: 4,
12+
[authorities.ADMIN]: 3,
13+
[authorities.MEMBER]: 2,
14+
[authorities.GUEST]: 1
15+
}
16+
17+
export const isAllowed = (user, requiredAuthority) => {
18+
if (user.superUser) {
19+
return true;
20+
}
21+
if (isEmpty(user.organizationMemberships)) {
22+
return false;
23+
}
24+
const mostImportantAuthority = user.organizationMemberships.reduce((max, membership) => {
25+
return authorityWeights[membership.authority] > authorityWeights[max.authority]
26+
? membership : max;
27+
}, {authority: authorities.GUEST})
28+
return mostImportantAuthority.authority >= authorityWeights[requiredAuthority]
29+
}
30+
31+
1032
export const allAuthorities = [authorities.GUEST, authorities.MEMBER, authorities.ADMIN];
1133

1234
export const getOrganizationMembership = (currentUser, organization, authority) => {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ public ResponseEntity<Application> create(User user, @Validated @RequestBody App
127127
confirmOrganizationMembership(user, organization, Authority.MEMBER);
128128
application.setCreatedAt(Instant.now());
129129
application.setCreatedBy(user.getName());
130+
application.setOwner(user);
130131
Application applicationSaved = applicationRepository.save(application);
131132

132133
Optional<OrganizationMembership> optionalOrganizationMembership = user.getOrganizationMemberships().stream()

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import access.repository.ApplicationRepository;
99
import access.repository.InvitationRepository;
1010
import access.repository.OrganizationRepository;
11+
import access.repository.UserRepository;
1112
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
1213
import org.apache.commons.logging.Log;
1314
import org.apache.commons.logging.LogFactory;
@@ -22,6 +23,7 @@
2223
import java.time.temporal.ChronoUnit;
2324
import java.util.List;
2425
import java.util.Map;
26+
import java.util.Optional;
2527
import java.util.Set;
2628
import java.util.stream.Collectors;
2729

@@ -42,15 +44,17 @@ public class InvitationController implements UserAccessRights {
4244
private final OrganizationRepository organizationRepository;
4345
private final ApplicationRepository applicationRepository;
4446
private final MailBox mailBox;
47+
private final UserRepository userRepository;
4548

4649
public InvitationController(InvitationRepository invitationRepository,
4750
OrganizationRepository organizationRepository,
4851
ApplicationRepository applicationRepository,
49-
MailBox mailBox) {
52+
MailBox mailBox, UserRepository userRepository) {
5053
this.invitationRepository = invitationRepository;
5154
this.organizationRepository = organizationRepository;
5255
this.applicationRepository = applicationRepository;
5356
this.mailBox = mailBox;
57+
this.userRepository = userRepository;
5458
}
5559

5660
@GetMapping({"/all/{organizationId}"})
@@ -117,18 +121,23 @@ public ResponseEntity<Map<String, Integer>> accept(User user, @Validated @Reques
117121
.orElseThrow(() -> new NotFoundException("Invitation not found"));
118122
invitation.accept();
119123

124+
user = reinitializeUser(user, userRepository);
120125
invitationRepository.save(invitation);
121-
//Now create organization_membership and - if any - applicationMemberships
122126
Organization organization = invitation.getOrganization();
123-
OrganizationMembership organizationMembership = new OrganizationMembership(user, organization, invitation.getIntendedAuthority());
124-
125-
126-
List<ApplicationMembership> applicationMemberships = invitation.getApplications().stream()
127-
.map(application -> new ApplicationMembership(application, organizationMembership, Authority.ADMIN))
128-
.toList();
129-
applicationMemberships.forEach(applicationMembership -> organizationMembership.addApplicationMembership(applicationMembership));
127+
//Internal users are already provisioned in their organization
128+
Optional<OrganizationMembership> organizationMembershipOptional = user.getOrganizationMemberships().stream()
129+
.filter(organizationMembership -> organizationMembership.getOrganization().getId().equals(organization.getId()))
130+
.findFirst();
131+
if (organizationMembershipOptional.isEmpty()) {
132+
//Now create organization_membership and - if any - applicationMemberships
133+
OrganizationMembership organizationMembership = new OrganizationMembership(user, organization, invitation.getIntendedAuthority());
134+
List<ApplicationMembership> applicationMemberships = invitation.getApplications().stream()
135+
.map(application -> new ApplicationMembership(application, organizationMembership, Authority.ADMIN))
136+
.toList();
137+
applicationMemberships.forEach(organizationMembership::addApplicationMembership);
138+
organization.addOrganizationMembership(organizationMembership);
139+
}
130140

131-
organization.addOrganizationMembership(organizationMembership);
132141
organizationRepository.save(organization);
133142

134143
return createResult();

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

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,8 @@ public ResponseEntity<Organization> find(User user,
7777

7878
Organization organization = organizationRepository.findDetailsById(id)
7979
.orElseThrow(() -> new NotFoundException("Organisation not found"));
80-
if (!user.isSuperUser()) {
81-
User userFromDB = reinitializeUser(user, userRepository);
82-
confirmOrganizationMembership(userFromDB, organization, Authority.MEMBER);
83-
}
80+
User userFromDB = reinitializeUser(user, userRepository);
81+
confirmOrganizationMembership(userFromDB, organization, Authority.MEMBER);
8482

8583
if (StringUtils.hasText(organization.getManageIdentifier()) && withIdp) {
8684
Map<String, Object> provider = manage.providerById(EntityType.saml20_idp, organization.getManageIdentifier(), Environment.PROD);
@@ -130,10 +128,8 @@ public ResponseEntity<Organization> light(User user, @PathVariable("id") Long id
130128
Organization organization = organizationRepository.findUsersById(id)
131129
.orElseThrow(() -> new NotFoundException("Organisation not found"));
132130

133-
if (!user.isSuperUser()) {
134-
User userFromDB = reinitializeUser(user, userRepository);
135-
confirmOrganizationMembership(userFromDB, organization, Authority.GUEST);
136-
}
131+
User userFromDB = reinitializeUser(user, userRepository);
132+
confirmOrganizationMembership(userFromDB, organization, Authority.GUEST);
137133

138134
return ResponseEntity.ok(organization);
139135
}
@@ -205,10 +201,9 @@ public ResponseEntity<Organization> update(User user, @RequestBody @Validated Or
205201
throw new InvalidInputException("Can not update name, Organization has manage identifier. " + organization.getName());
206202
}
207203

208-
if (!user.isSuperUser()) {
209-
User userFromDB = reinitializeUser(user, userRepository);
210-
confirmOrganizationMembership(userFromDB, organization, Authority.ADMIN);
211-
}
204+
User userFromDB = reinitializeUser(user, userRepository);
205+
confirmOrganizationMembership(userFromDB, organization, Authority.ADMIN);
206+
212207
organization.setName(organizationForm.getName());
213208
Organization savedOrganization = organizationRepository.save(organization);
214209
return ResponseEntity.status(HttpStatus.CREATED).body(savedOrganization);
@@ -238,11 +233,8 @@ public ResponseEntity<Organization> updateMetaData(User user,
238233
if (!StringUtils.hasText(organization.getManageIdentifier())) {
239234
throw new InvalidInputException("Can not update metadata. Organization has no manage identifier: " + organization.getName());
240235
}
241-
242-
if (!user.isSuperUser()) {
243-
User userFromDB = reinitializeUser(user, userRepository);
244-
confirmOrganizationMembership(userFromDB, organization, Authority.ADMIN);
245-
}
236+
User userFromDB = reinitializeUser(user, userRepository);
237+
confirmOrganizationMembership(userFromDB, organization, Authority.ADMIN);
246238

247239
organization.setMetaData(metaData);
248240
manage.saveIdentityProvider(organization);
@@ -256,10 +248,9 @@ public ResponseEntity<Map<String, Integer>> delete(User user, @PathVariable("org
256248

257249
Organization organization = organizationRepository.findById(organizationId)
258250
.orElseThrow(() -> new NotFoundException("Organization not found"));
259-
if (!user.isSuperUser()) {
260-
user = this.reinitializeUser(user, userRepository);
261-
confirmOrganizationMembership(user, organization, Authority.ADMIN);
262-
}
251+
252+
user = this.reinitializeUser(user, userRepository);
253+
confirmOrganizationMembership(user, organization, Authority.ADMIN);
263254

264255
organizationRepository.deleteOrganizationById(organizationId);
265256

server/src/main/java/access/model/Application.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public class Application implements NameHolder {
7272

7373
@ManyToOne(fetch = FetchType.LAZY)
7474
@JoinColumn(name = "owner_id")
75-
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
75+
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
7676
private User owner;
7777

7878
@Enumerated(EnumType.STRING)
@@ -101,6 +101,16 @@ public Map<String, Serializable> getOrganizationInfo() {
101101
return organizationInfo;
102102
}
103103

104+
//We need info, about the owner
105+
@JsonProperty(access = JsonProperty.Access.READ_ONLY, value = "ownerIdentifier")
106+
public Long getOwnerInfo() {
107+
User appOwner = getOwner();
108+
if (appOwner != null && Hibernate.isInitialized(appOwner)) {
109+
return appOwner.getId();
110+
}
111+
return null;
112+
}
113+
104114
@JsonIgnore
105115
public void removeConnection(Connection connection) {
106116
//This is required by Hibernate - children can't be dereferenced

0 commit comments

Comments
 (0)