diff --git a/client/src/api/index.js b/client/src/api/index.js
index 3b0e3db2c..1ee68e98a 100644
--- a/client/src/api/index.js
+++ b/client/src/api/index.js
@@ -337,7 +337,10 @@ export function resetOidcClientSecret(service) {
}
export function resetScimBearerToken(service, scim_bearer_token) {
- const body = {scim_bearer_token: scim_bearer_token}
+ const body = {
+ scim_bearer_token: scim_bearer_token,
+ scim_url: service.scim_url
+ };
return postPutJson(`/api/services/reset_scim_bearer_token/${service.id}`, body, "put");
}
diff --git a/client/src/locale/en.js b/client/src/locale/en.js
index 3fc94fdb6..fa4682dc8 100644
--- a/client/src/locale/en.js
+++ b/client/src/locale/en.js
@@ -1060,7 +1060,9 @@ const en = {
},
scim_token: {
preTitle: "For security reasons, the current SCIM token can not be displayed. ",
+ preTitleNoToken: "The SCIM push requires a bearer security token. ",
title: "Change the SCIM token.",
+ titleNoToken: "Add a SCIM token.",
confirmation: "Please enter the SCIM token for {{name}}. It will be stored encrypted.",
success: "SCIM token has been updated.",
},
diff --git a/client/src/locale/nl.js b/client/src/locale/nl.js
index 5f18180a5..07384a131 100644
--- a/client/src/locale/nl.js
+++ b/client/src/locale/nl.js
@@ -1060,7 +1060,9 @@ const nl = {
},
scim_token: {
preTitle: "Om veiligheidsredenen kan het huidige SCIM token niet worden weergegeven. ",
+ preTitleNoToken: "Een SCIM push vereist een bearer security token. ",
title: "Verander het SCIM token.",
+ titleNoToken: "Voeg een SCIM token toe.",
confirmation: "Voer het SCIM token in voor {{name}}. Het zal encrypted worden opgeslagen.",
success: "SCIM token is veranderd.",
},
diff --git a/client/src/pages/ServiceOverview.jsx b/client/src/pages/ServiceOverview.jsx
index b3e042ae1..4ff97ae05 100644
--- a/client/src/pages/ServiceOverview.jsx
+++ b/client/src/pages/ServiceOverview.jsx
@@ -371,17 +371,19 @@ class ServiceOverview extends React.Component {
confirmationDialogQuestion: I18n.t("service.scim_token.confirmation", {name: service.name}),
confirmationTxt: I18n.t("confirmationDialog.confirm"),
});
-
} else {
const {scimBearerToken} = this.state;
- resetScimBearerToken(service, scimBearerToken).then(() => {
- setFlash(I18n.t("service.scim_token.success"));
- this.setState({
- confirmationDialogOpen: false,
- scimTokenChange: false,
- loading: false
- });
- })
+ resetScimBearerToken(service, scimBearerToken)
+ .then(() => {
+ setFlash(I18n.t("service.scim_token.success"));
+ this.setState({
+ confirmationDialogOpen: false,
+ scimTokenChange: false,
+ loading: false,
+ scimBearerToken: null,
+ "service": {...service, has_scim_bearer_token: true}
+ });
+ })
}
}
@@ -862,7 +864,8 @@ class ServiceOverview extends React.Component {
});
}
- renderButtons = (isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken, service) => {
+ renderButtons = (isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken,
+ service, invalidInputs) => {
const invalidTabsMsg = this.getInvalidTabs();
return <>
{!createNewServiceToken &&
@@ -875,7 +878,7 @@ class ServiceOverview extends React.Component {
{currentTab === "SCIMServer" &&
this.doSweep(service)}/>
}/>}
}
@@ -1805,7 +1811,8 @@ class ServiceOverview extends React.Component {
{I18n.t(`serviceDetails.toc.${currentTab}`)}
{this.renderCurrentTab(config, currentTab, service, alreadyExists, isAdmin, isServiceAdmin, disabledSubmit,
invalidInputs, hasAdministrators, showServiceAdminView, createNewServiceToken, initial, crmOrganisations)}
- {this.renderButtons(isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView, createNewServiceToken, service)}
+ {this.renderButtons(isAdmin, isServiceAdmin, disabledSubmit, currentTab, showServiceAdminView,
+ createNewServiceToken, service, invalidInputs)}
);
}
diff --git a/server/api/service.py b/server/api/service.py
index ce315866b..5bdc513c7 100644
--- a/server/api/service.py
+++ b/server/api/service.py
@@ -294,7 +294,8 @@ def service_by_id(service_id):
.options(selectinload(Service.service_tokens)) \
.options(selectinload(Service.service_groups))
service = query.filter(Service.id == service_id).one()
- return service, 200
+ else:
+ service = query.filter(Service.id == service_id).one()
if api_call:
query = query \
.options(selectinload(Service.ip_networks))
@@ -303,7 +304,9 @@ def service_by_id(service_id):
del res["logo"]
return res, 200
- return query.filter(Service.id == service_id).one(), 200
+ res = jsonify(service).json
+ res["has_scim_bearer_token"] = service.scim_bearer_token_db_value() is not None
+ return res, 200
@service_api.route("/all", strict_slashes=False)
@@ -580,7 +583,7 @@ def update_service():
for attr in [fb for fb in forbidden if fb in data]:
data[attr] = getattr(service, attr)
- for attr in ["sweep_scim_last_run", "ldap_password", "scim_bearer_token", "oidc_client_secret"]:
+ for attr in ["sweep_scim_last_run", "ldap_password", "scim_bearer_token", "oidc_client_secret", "exported_at"]:
if attr in data:
del data[attr]
@@ -596,15 +599,24 @@ def update_service():
scim_url_changed = data.get("scim_url", None) != service.scim_url and bool(service.scim_bearer_token_db_value())
# Before we update we need to get the unencrypted bearer_token
- if scim_url_changed:
+ scim_enabled = data.get("scim_enabled", False)
+ if scim_url_changed and scim_enabled:
plain_bearer_token = decrypt_scim_bearer_token(service)
+ if not scim_enabled:
+ # Clean up the Scim related attributes
+ data["scim_url"] = None
+ data["scim_bearer_token"] = None
+ data["sweep_scim_enabled"] = False
+ data["sweep_remove_orphans"] = False
+ data["sweep_scim_daily_rate"] = None
+
res = update(Service, custom_json=data, allow_child_cascades=False, allowed_child_collections=["ip_networks"])
service = res[0]
sync_external_service(current_app, service)
- if scim_url_changed and service.scim_enabled:
+ if scim_url_changed and scim_enabled:
service.scim_bearer_token = plain_bearer_token
encrypt_scim_bearer_token(service)
@@ -683,7 +695,9 @@ def reset_oidc_client_secret(service_id):
def reset_scim_bearer_token(service_id):
confirm_service_admin(service_id)
service = db.session.get(Service, service_id)
- # Ensure we only change the scim_bearer_token
- service.scim_bearer_token = current_request.get_json()["scim_bearer_token"]
+ # Ensure we only change the scim_bearer_token and optional the url
+ data = current_request.get_json()
+ service.scim_bearer_token = data.get("scim_bearer_token")
+ service.scim_url = data.get("scim_url", service.scim_url)
encrypt_scim_bearer_token(service)
return {}, 201
diff --git a/server/test/api/test_service.py b/server/test/api/test_service.py
index e51bdfecb..40ad0ff1a 100644
--- a/server/test/api/test_service.py
+++ b/server/test/api/test_service.py
@@ -10,7 +10,7 @@
from server.test.seed import service_mail_name, service_network_entity_id, unihard_name, \
service_network_name, service_scheduler_name, service_wiki_name, service_storage_name, \
service_cloud_name, service_storage_entity_id, service_ssh_name, unifra_name, unihard_secret, \
- user_jane_name, user_roger_name, service_sram_demo_sp, umcpekela_name
+ user_jane_name, user_roger_name, service_sram_demo_sp, umcpekela_name, service_monitor_name
class TestService(AbstractTest):
@@ -63,6 +63,12 @@ def test_find_by_id_service_admin(self):
service_details = self.get(f"api/services/{service.id}", response_status_code=200, with_basic_auth=False)
self.assertEqual(unihard_name, service_details["organisation_name"])
+ def test_find_by_id_service_admin_has_bearer_token(self):
+ service = self.find_entity_by_name(Service, service_monitor_name)
+ self.login("urn:service_admin")
+ service_details = self.get(f"api/services/{service.id}", response_status_code=200, with_basic_auth=False)
+ self.assertTrue(service_details["has_scim_bearer_token"])
+
def test_find_by_id_api_call(self):
service = self.find_entity_by_name(Service, service_scheduler_name)
service = self.get(f"api/services/{service.id}")
@@ -696,7 +702,7 @@ def test_service_update_scim_secret_exception(self):
with self.assertLogs() as cm:
self.put(f"/api/services/reset_scim_bearer_token/{service['id']}",
{"scim_bearer_token": "somethingelse"}, response_status_code=400)
- self.assertIn('"encrypt_scim_bearer_token requires scim_bearer_token and scim_url"', "\n".join(cm.output))
+ self.assertIn('"encrypt_scim_bearer_token requires scim_bearer_token and scim_url"', "\n".join(cm.output))
def test_access_allowed_for_crm_organisation(self):
service = self.find_entity_by_name(Service, service_cloud_name)
diff --git a/server/test/seed.py b/server/test/seed.py
index f40e51172..6e1b03c0f 100644
--- a/server/test/seed.py
+++ b/server/test/seed.py
@@ -88,6 +88,7 @@
service_storage_entity_id = "https://storage"
service_cloud_entity_id = "https://cloud"
service_scheduler_entity_id = "uuc_scheduler_entity_id"
+service_monitor_name = "LDAP/SCIM Monitor Service"
service_storage_name = "Storage"
service_wireless_name = "Wireless"
@@ -461,7 +462,7 @@ def seed(db, app_config, skip_seed=False):
persist_instance(db, mail, wireless, cloud, storage, wiki, network, service_ssh, uuc_scheduler,
service_empty, demo_sp, demo_rp)
- service_monitor = Service(entity_id="https://ldap-monitor.example.org", name="LDAP/SCIM Monitor Service",
+ service_monitor = Service(entity_id="https://ldap-monitor.example.org", name=service_monitor_name,
description="Used for monitoring LDAP and SCIM. NIET AANKOMEN.",
override_access_allowed_all_connections=False, automatic_connection_allowed=True,
logo=read_image("ldap.png"),