Skip to content

Commit

Permalink
Fixes #1067
Browse files Browse the repository at this point in the history
  • Loading branch information
oharsta committed Nov 22, 2023
1 parent bd97047 commit d7d63cd
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 12 deletions.
4 changes: 2 additions & 2 deletions client/src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ function fetchJson(path, options = {}, headers = {}, showErrorDialog = true) {
.then(res => res.json());
}

function postPutJson(path, body, method, showErrorDialog = true) {
function postPutJson(path, body, method, showErrorDialog = true, headers = {}) {
const jsonBody = JSON.stringify(body);
return fetchJson(path, {method: method, body: jsonBody}, {}, showErrorDialog);
return fetchJson(path, {method: method, body: jsonBody}, headers, showErrorDialog);
}

function fetchDelete(path, showErrorDialog = true) {
Expand Down
9 changes: 8 additions & 1 deletion client/src/locale/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,13 @@ const en = {
manuallyApprove: "I want to approve requests manually",
depends: "It depends",
settingsPerInstitution: "I'll choose for each organisation below"
},
sweep: {
test: "Test SCIM",
testTooltip: "Test the SCIM endpoint and the SCIM token",
success: "The SCIM connection test to {{url}} was successful",
failure: "The SCIM connection test to {{url}} was unsuccessful.",
response: "Response from the SCIM endpoint:"
}
},
organisation: {
Expand Down Expand Up @@ -1418,7 +1425,7 @@ const en = {
groups: "Group membership",
requiredEmail: "At least one email address is required for an invitation for a collaboration.",
requiredRole: "You must choose the intended role for the collaboration membership.",
requiredExpiryDate:"The expiry date for an invitation is required",
requiredExpiryDate: "The expiry date for an invitation is required",
message: "Message",
messagePlaceholder: "Personal message to the admins",
messageTooltip: "The message will be included in the email invitation to the admins.",
Expand Down
7 changes: 7 additions & 0 deletions client/src/locale/nl.js
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,13 @@ const nl = {
manuallyApprove: "Ik wil koppelverzoeken handmatig goedkeuren",
depends: "Hangt er vanaf",
settingsPerInstitution: "Ik kies hieronder per organisatie"
},
sweep: {
test: "Test SCIM",
testTooltip: "Test het SCIM endpoint en het SCIM token",
success: "De SCIM connectie test naar {{url}} was succesvol",
failure: "De SCIM connectie test naar {{url}} was niet succesvol.",
response: "Antwoord van het SCIM endpoint:"
}
},
organisation: {
Expand Down
79 changes: 73 additions & 6 deletions client/src/pages/ServiceOverview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ import {
createServiceToken,
deleteService,
deleteServiceToken,
ipNetworks, requestDeleteService,
ipNetworks,
requestDeleteService,
resetLdapPassword,
serviceAbbreviationExists,
serviceAupDelete,
serviceEntityIdExists,
serviceNameExists,
serviceTokenValue,
sweep,
updateService
} from "../api";
import I18n from "../locale/I18n";
import {ReactComponent as CriticalIcon} from "@surfnet/sds/icons/functional-icons/alert-circle.svg";
import {ReactComponent as ThrashIcon} from "@surfnet/sds/icons/functional-icons/bin.svg";
import {ReactComponent as CheckIcon} from "@surfnet/sds/icons/functional-icons/success.svg";
import InputField from "../components/InputField";
import "./ServiceOverview.scss";
import "../components/redesign/ApiKeys.scss";
Expand Down Expand Up @@ -69,7 +73,9 @@ class ServiceOverview extends React.Component {
description: "",
hashedToken: null,
validatingNetwork: false,
initial: true
initial: true,
sweepSuccess: null,
sweepResults: null
}
}

Expand Down Expand Up @@ -366,6 +372,43 @@ class ServiceOverview extends React.Component {
return valid;
};

doSweep = service => {
this.setState({loading: true});
sweep(service).then(res => {
this.setState({
loading: false,
sweepSuccess: true,
sweepResults: res,
confirmationDialogQuestion: null,
confirmationDialogAction: () => this.setState({confirmationDialogOpen: false}),
confirmationHeader: I18n.t("service.sweep.test"),
confirmationTxt: I18n.t("confirmationDialog.ok"),
warning: false,
cancelDialogAction: null,
confirmationDialogOpen: true
});
}).catch(e => {
this.setState({
loading: false,
sweepSuccess: false,
confirmationDialogQuestion: null,
confirmationDialogAction: () => this.setState({confirmationDialogOpen: false}),
confirmationHeader: I18n.t("service.sweep.test"),
confirmationTxt: I18n.t("confirmationDialog.ok"),
warning: false,
cancelDialogAction: null,
confirmationDialogOpen: true
});
if (e.response && e.response.json) {
e.response.json().then(res => {
this.setState({
sweepResults: res,
});
})
}
})
}

isValidTab = tab => {
const {alreadyExists, invalidInputs, hasAdministrators, service, initial} = this.state;
const {contact_email, ip_networks} = service;
Expand Down Expand Up @@ -481,7 +524,9 @@ class ServiceOverview extends React.Component {
stopEvent(e);
this.setState({
currentTab: tab,
createNewServiceToken: false
createNewServiceToken: false,
sweepSuccess: null,
sweepResults: null,
}, () => this.props.history.replace(`/services/${this.state.service.id}/details/${tab}`));
}

Expand Down Expand Up @@ -530,7 +575,7 @@ class ServiceOverview extends React.Component {
};


renderButtons = (isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken) => {
renderButtons = (isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken, service) => {
const invalidTabsMsg = this.getInvalidTabs();
return <>
{((isAdmin || isServiceAdmin) && !createNewServiceToken) && <div className={"actions-container"}>
Expand All @@ -539,6 +584,12 @@ class ServiceOverview extends React.Component {
{((isAdmin || isServiceAdmin) && currentTab === "general") &&
<Button warningButton={true}
onClick={this.delete}/>}
{((isAdmin || isServiceAdmin) && currentTab === "SCIMServer") &&
<Tooltip tip={I18n.t("service.sweep.testTooltip")} children={
<Button txt={I18n.t("service.sweep.test")}
disabled={!service.scim_enabled}
onClick={() => this.doSweep(service)}/>
}/>}
<Button disabled={disabledSubmit} txt={I18n.t("service.update")}
onClick={this.submit}/>
</section>
Expand Down Expand Up @@ -586,6 +637,19 @@ class ServiceOverview extends React.Component {
</div>)
}

renderScimResults = (service, sweepSuccess, sweepResults) => {
return (
<div className="sweep-results">
<p className={sweepSuccess ? "success" : "failure"}>{sweepSuccess ? <CheckIcon/> : <CriticalIcon/>}
{I18n.t(`service.sweep.${sweepSuccess ? "success" : "failure"}`, {url: service.scim_url})}</p>
{(!isEmpty(sweepResults) && sweepResults.error) && <div className="response">
<p>{I18n.t("service.sweep.response")}</p>
<em>{sweepResults.error}</em>
</div>}
</div>
);
}

renderSCIMServer = (service, isAdmin, showServiceAdminView, alreadyExists, invalidInputs, initial) => {
let sweepScimDailyRate = null;
if (service.sweep_scim_enabled && service.sweep_scim_daily_rate && service.sweep_scim_daily_rate.value) {
Expand Down Expand Up @@ -1163,7 +1227,9 @@ class ServiceOverview extends React.Component {
ldapPassword,
tokenValue,
createNewServiceToken,
initial
initial,
sweepResults,
sweepSuccess
} = this.state;
if (loading) {
return <SpinnerField/>
Expand All @@ -1181,13 +1247,14 @@ class ServiceOverview extends React.Component {
confirm={confirmationDialogAction}
question={confirmationDialogQuestion}>
{ldapPassword && this.renderLdapPassword(ldapPassword)}
{!isEmpty(sweepSuccess) && this.renderScimResults(service, sweepSuccess, sweepResults)}
</ConfirmationDialog>

{this.sidebar(currentTab)}
<div className={`service ${createNewServiceToken ? "no-grid" : ""}`}>
<h2 className="section-separator">{I18n.t(`serviceDetails.toc.${currentTab}`)}</h2>
{this.renderCurrentTab(config, currentTab, service, alreadyExists, isAdmin, isServiceAdmin, disabledSubmit, invalidInputs, hasAdministrators, showServiceAdminView, createNewServiceToken, initial)}
{this.renderButtons(isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken)}
{this.renderButtons(isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken, service)}
</div>
</div>);
}
Expand Down
23 changes: 23 additions & 0 deletions client/src/pages/ServiceOverview.scss
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,29 @@ section.dialog-content {
}
}

.sweep-results {
p {
display: flex;
align-items: center;

svg {
width: 32px;
height: auto;
margin-right: 8px;
}
&.success {
svg {
color: var(--sds--color--green--400);
}
}
&.failure {
svg {
color: var(--sds--color--red--400);
}
}
}
}

.service-overview {
max-width: $medium;
margin: 40px auto 25px auto;
Expand Down
7 changes: 4 additions & 3 deletions server/api/scim.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from werkzeug.exceptions import Unauthorized, BadRequest

from server.api.base import json_endpoint, query_param
from server.auth.security import confirm_write_access
from server.auth.security import confirm_write_access, is_service_admin
from server.auth.tokens import validate_service_token
from server.db.db import db
from server.db.defaults import SERVICE_TOKEN_SCIM
Expand Down Expand Up @@ -142,8 +142,9 @@ def sweep():
try:
service = validate_service_token("scim_enabled", SERVICE_TOKEN_SCIM)
except Unauthorized:
confirm_write_access()
service = db.session.get(Service, query_param("service_id"))
service_id = query_param("service_id")
confirm_write_access(service_id, override_func=is_service_admin)
service = db.session.get(Service, service_id)
try:
return perform_sweep(service), 201
except BadRequest as error:
Expand Down

0 comments on commit d7d63cd

Please sign in to comment.