Repository: rspace-os/rspace-web
Pull request #343: RSDEV-653: oauth authentication enabled by system setting
Author: login: mKowalski256
Created: 2025-05-22T11:22:43Z | Merged: 2025-05-23T06:30:48Z

## Description ##
The PR adds new system setting `api.oauth.authentication`, which is set to DENIED by default. That stops oauth authentication endpoint from generating tokens, unless Sysadmin explicitly allows it.

The setting is available just for System Admins (not on Community level), on System -> Configuration -> System Settings page;
![image](https://github.com/user-attachments/assets/a3cf1928-4d43-46ab-8979-5152f5a60da3)

## Testing notes
Can be tested by trying OAuth login option in swagger docs (at `/public/apiDocs`).


Diff:
diff --git a/src/main/java/com/researchspace/api/v1/controller/OAuthClientController.java b/src/main/java/com/researchspace/api/v1/controller/OAuthClientController.java
index a15fa7d2cf..9e6eff5e34 100644
--- a/src/main/java/com/researchspace/api/v1/controller/OAuthClientController.java
+++ b/src/main/java/com/researchspace/api/v1/controller/OAuthClientController.java
@@ -11,6 +11,8 @@
 import com.researchspace.service.ApiAvailabilityHandler;
 import com.researchspace.service.IReauthenticator;
 import com.researchspace.service.OAuthTokenManager;
+import com.researchspace.service.SystemPropertyName;
+import com.researchspace.service.SystemPropertyPermissionManager;
 import com.researchspace.service.UserManager;
 import com.researchspace.webapp.controller.IgnoreInLoggingInterceptor;
 import javax.servlet.http.HttpServletRequest;
@@ -44,6 +46,8 @@ public class OAuthClientController {
 
   @Autowired private AnalyticsManager analyticsMgr;
 
+  private @Autowired SystemPropertyPermissionManager systemPropertyMgr;
+
   /** Main endpoint for token grants. New or refreshed. */
   @PostMapping("/token")
   @IgnoreInLoggingInterceptor(ignoreRequestParams = {"client_secret", "password"})
@@ -68,6 +72,14 @@ public NewOAuthTokenResponse getToken(
           "Access to API has been disabled by RSpace administrator.");
     }
 
+    boolean oauthAuthenticationEnabled =
+        systemPropertyMgr.isPropertyAllowed(
+            (User) null, SystemPropertyName.API_OAUTH_AUTHENTICATION);
+    if (!oauthAuthenticationEnabled) {
+      throw new ApiAuthenticationException(
+          "OAuth authentication has been disabled by RSpace administrator.");
+    }
+
     NewOAuthTokenResponse response;
     if ("password".equals(grantType)) {
       if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
diff --git a/src/main/java/com/researchspace/service/SystemPropertyName.java b/src/main/java/com/researchspace/service/SystemPropertyName.java
index 2d9aa98c12..62a660340f 100644
--- a/src/main/java/com/researchspace/service/SystemPropertyName.java
+++ b/src/main/java/com/researchspace/service/SystemPropertyName.java
@@ -5,6 +5,7 @@
 @Getter
 public enum SystemPropertyName {
   API_AVAILABLE("api.available"),
+  API_OAUTH_AUTHENTICATION("api.oauth.authentication"),
   BOX_AVAILABLE("box.available"),
   CHEMISTRY_AVAILABLE("chemistry.available"),
   DATACITE_ENABLED("datacite.enabled"),
diff --git a/src/main/resources/bundles/system/system.properties b/src/main/resources/bundles/system/system.properties
index 129e3689cb..1430f3993f 100644
--- a/src/main/resources/bundles/system/system.properties
+++ b/src/main/resources/bundles/system/system.properties
@@ -326,6 +326,8 @@ system.property.description.chemistry.available=Makes chemistry integration avai
   users can create and edit chemical sketches, and search for chemical structures in the Workspace
 system.property.description.slack.available=Makes Slack integration available to the users. After enabling the integration, \
   user can connect to their Slack channels to send messages or forward notifications
+system.property.description.msteams.available=Makes MS Teams integration available to the users. After enabling the integration, \
+  user can connect to their MS Teams channels to send messages or forward notifications
 system.property.description.orcid.available=Enables ORCID integration. After enabling the integration, \
   users can set up their ORCID ID on 'My Profile' page.
 system.property.description.repo.available=Makes {0} repository integration available. After enabling, \
@@ -334,12 +336,8 @@ system.property.description.pyrat.available=Makes PyRAT integration available. A
   users can link to animals in the PyRAT database.
 system.property.description.clustermarket.available=Makes Clustermarket integration available. After enabling the integration, \
   users can link to data regarding laboratory equipment.
-system.property.description.clustermarket.enabled=Enables linking to Clustermarket in text field editor. \
-  Relevant only if 'clustermarket.available' is 'true'.
 system.property.description.omero.available=Makes Omero integration available. After enabling the integration, \
   users can link to Omero image data.
-system.property.description.omero.enabled=Enables linking to Omero in text field editor. \
-  Relevant only if 'omero.available' is 'true'.
 system.property.description.jove.available=Makes JoVE integration available. User can search for and insert videos/articles from JoVE.
 system.property.description.dryad.available=Makes Dryad integration available. User can create new dryad submission\
   and attach RSpace exports or documents to that submission.
@@ -350,18 +348,22 @@ system.property.description.argos.available=Makes Argos integration available.
 system.property.description.zenodo.available=Makes Zenodo integration available.
 system.property.description.fieldmark.available=Makes Fieldmark integration available.
 system.property.description.galaxy.available=Makes Galaxy integration available.
-system.property.description.digital_commons_data.available=Makes Digital Commons Data integration available.
 system.property.description.github.available=Makes Github integration available. After enabling, user can add links \
   to files in Github repositories.
+system.property.description.protocols_io.available=Makes Protocols.io integration available, allowing users to create documents from \
+  the protocols available through that service.
 system.property.description.api.available=Enables API access for users
+system.property.description.api.oauth.authentication=Enables accessing public API with OAuth authentication flow
 system.property.description.onboarding.available=Enables onboarding announcements across RSpace.
 system.property.description.snapgene.available=Enables Snapgene viewer for DNA sequence files. This must only be enabled if the deployment property\
  'snapgene.web.url' is configured to point at an RSpace-Snapgene web-service.
-system.property.description.group_autosharing.available=Enables the management of group-wide autosharing.\
+system.property.description.group_autosharing.available=Enables the management of group-wide autosharing. \
  Enables PIs and lab admins with the 'View All' permission to manage the autoshare status for non-PI lab members.
-system.property.description.self_service_labgroups=When true, a user with permissions (eg a PI) can create a LabGroup
-system.property.description.allow_project_groups=When true, a user can create a group without a PI
+system.property.description.self_service_labgroups=When true, a user with permissions (e.g. a PI) can create a LabGroup.
+system.property.description.allow_project_groups=When true, users can create Project Groups (which don't have a PI)
 system.property.description.publicLastLogin.available=If enabled, a user's last login time is publicly viewable on their profile page.
-system.property.description.inventory.available=Enables Inventory module
-system.property.description.public_sharing=Enables Publication of Documents
-system.property.description.publicdocs_allow_seo=Enables search engine indexing of published documents
+system.property.description.inventory.available=Enables Inventory module.
+system.property.description.public_sharing=Enables Publication of Documents.
+system.property.description.publicdocs_allow_seo=Enables search engine indexing of published documents.
+system.property.description.pi_can_edit_all_work_in_labgroup=Gives PI edit access to all data in their LabGroup.
+
diff --git a/src/main/resources/sqlUpdates/changeLog-rsdev-365.xml b/src/main/resources/sqlUpdates/changeLog-rsdev-365.xml
new file mode 100644
index 0000000000..f1f0aec0e8
--- /dev/null
+++ b/src/main/resources/sqlUpdates/changeLog-rsdev-365.xml
@@ -0,0 +1,31 @@
+<databaseChangeLog
+        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
+         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.0.xsd">
+
+    <changeSet id="2025-05-21" author="matthias">
+        <comment>Create a new system property to toggle OAuth authentication for public API</comment>
+        <insert tableName="PropertyDescriptor">
+            <column name="id" type="NUMERIC" value="NULL"/>
+            <column name="defaultValue" type="STRING" value="DENIED"/>
+            <column name="name" type="STRING" value="api.oauth.authentication"/>
+            <column name="type" type="NUMERIC" value="3"/>
+        </insert>
+        <insert tableName="SystemProperty">
+            <column name="id" type="NUMERIC" value="NULL"/>
+            <column name="dependent_id" type="NUMERIC" value="NULL"/>
+            <column name="descriptor_id"
+                    type="NUMERIC"
+                    valueComputed="(select id from PropertyDescriptor where name ='api.oauth.authentication')"/>
+        </insert>
+        <insert tableName="SystemPropertyValue">
+            <column name="id" type="NUMERIC" value="NULL"/>
+            <column name="value" type="String" value="DENIED"/>
+            <column name="property_id"
+                    type="NUMERIC"
+                    valueComputed="(select sp.id from SystemProperty sp inner join PropertyDescriptor pd on sp.descriptor_id=pd.id where pd.name='api.oauth.authentication')"/>
+        </insert>
+    </changeSet>
+
+</databaseChangeLog>
diff --git a/src/main/resources/sqlUpdates/liquibase-master.xml b/src/main/resources/sqlUpdates/liquibase-master.xml
index 2381ace963..ce95436ef2 100644
--- a/src/main/resources/sqlUpdates/liquibase-master.xml
+++ b/src/main/resources/sqlUpdates/liquibase-master.xml
@@ -143,7 +143,7 @@
     <include relativeToChangelogFile="true" file="changeLog-rsdev-435.xml"/>
     <include relativeToChangelogFile="true" file="changeLog-rsdev-607.xml"/>
     <include relativeToChangelogFile="true" file="changeLog-rsdev-638.xml"/>
-
+    <include relativeToChangelogFile="true" file="changeLog-rsdev-365.xml"/>
 
   <!--  ensure that customUpdates-changeLog.xml always runs LAST! -->
     <include relativeToChangelogFile="true" file="customUpdates-changeLog.xml"/>
diff --git a/src/main/webapp/WEB-INF/pages/system/settings_ajax.jsp b/src/main/webapp/WEB-INF/pages/system/settings_ajax.jsp
index 94b72cc410..93f7bc05b2 100644
--- a/src/main/webapp/WEB-INF/pages/system/settings_ajax.jsp
+++ b/src/main/webapp/WEB-INF/pages/system/settings_ajax.jsp
@@ -79,15 +79,19 @@
         <div id="fieldmark.available.description"><spring:message code="system.property.description.fieldmark.available" /></div>
         <div id="github.available.description"><spring:message code="system.property.description.github.available" /></div>
         <div id="api.available.description"><spring:message code="system.property.description.api.available" /></div>
+        <div id="api.oauth.authentication.description"><spring:message code="system.property.description.api.oauth.authentication" /></div>
         <div id="onboarding.available.description"><spring:message code="system.property.description.onboarding.available" /></div>
         <div id="snapgene.available.description"><spring:message code="system.property.description.snapgene.available" /></div>
         <div id="group_autosharing.available.description"><spring:message code="system.property.description.group_autosharing.available" /></div>
-        <div id="self_service_labgroups.available.description"><spring:message code="system.property.description.self_service_labgroups" /></div>
+        <div id="self_service_labgroups.description"><spring:message code="system.property.description.self_service_labgroups" /></div>
         <div id="publicLastLogin.available.description"><spring:message code="system.property.description.publicLastLogin.available" /></div>
         <div id="inventory.available.description"><spring:message code="system.property.description.inventory.available" /></div>
         <div id="public_sharing.description"><spring:message code="system.property.description.public_sharing" /></div>
         <div id="publicdocs_allow_seo.description"><spring:message code="system.property.description.publicdocs_allow_seo" /></div>
         <div id="allow_project_groups.description"><spring:message code="system.property.description.allow_project_groups" /></div>
+        <div id="pi_can_edit_all_work_in_labgroup.description"><spring:message code="system.property.description.pi_can_edit_all_work_in_labgroup" /></div>
+        <div id="msteams.available.description"><spring:message code="system.property.description.msteams.available" /></div>
+        <div id="protocols_io.available.description"><spring:message code="system.property.description.protocols_io.available" /></div>
     </div>
 
 </div>
diff --git a/src/main/webapp/scripts/pages/system/settings_mod.js b/src/main/webapp/scripts/pages/system/settings_mod.js
index 1424ce7635..7852f3b1a5 100644
--- a/src/main/webapp/scripts/pages/system/settings_mod.js
+++ b/src/main/webapp/scripts/pages/system/settings_mod.js
@@ -33,6 +33,7 @@ define(function() {
 
         _printSubCategory('RSpace API');
         _printSettings([ 'api.available' ]);
+        _printSettings([ 'api.oauth.authentication' ]);
 
         _printSubCategory('Lab Group Settings');
         _printSettings([ 'pi_can_edit_all_work_in_labgroup' ]);
diff --git a/src/test/java/com/researchspace/webapp/controller/MVCTestBase.java b/src/test/java/com/researchspace/webapp/controller/MVCTestBase.java
index 70e2649972..8a2a2e7699 100644
--- a/src/test/java/com/researchspace/webapp/controller/MVCTestBase.java
+++ b/src/test/java/com/researchspace/webapp/controller/MVCTestBase.java
@@ -69,6 +69,20 @@ public void enableGlobalApiAccess() {
         SystemPropertyName.API_AVAILABLE, HierarchicalPermission.ALLOWED, getSysAdminUser());
   }
 
+  public void disableApiOAuthAuthentication() {
+    sysPropMgr.save(
+        SystemPropertyName.API_OAUTH_AUTHENTICATION,
+        HierarchicalPermission.DENIED,
+        getSysAdminUser());
+  }
+
+  public void enableApiOAuthAuthentication() {
+    sysPropMgr.save(
+        SystemPropertyName.API_OAUTH_AUTHENTICATION,
+        HierarchicalPermission.ALLOWED,
+        getSysAdminUser());
+  }
+
   /**
    * Mimics edit lock request, autosave and save, and unlock behaviour from editor via MVC
    * /controller test.
diff --git a/src/test/java/com/researchspace/webapp/controller/OAuthAPIAccessMVCIT.java b/src/test/java/com/researchspace/webapp/controller/OAuthAPIAccessMVCIT.java
index d9e08f913c..abc3c21db8 100644
--- a/src/test/java/com/researchspace/webapp/controller/OAuthAPIAccessMVCIT.java
+++ b/src/test/java/com/researchspace/webapp/controller/OAuthAPIAccessMVCIT.java
@@ -13,32 +13,34 @@
 import com.researchspace.testutils.RSpaceTestUtils;
 import org.junit.Test;
 import org.springframework.test.web.servlet.MvcResult;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 
 public class OAuthAPIAccessMVCIT extends API_MVC_TestBase {
+
   @Test
   public void createAccessTokenAndAccessApi() throws Exception {
+    enableGlobalApiAccess();
+    enableApiOAuthAuthentication();
+
     User user = createInitAndLoginAnyUser();
     RSpaceTestUtils.logout();
 
-    // Get access token
-    enableGlobalApiAccess();
-    MvcResult result =
-        mockMvc
-            .perform(
-                post("/oauth/token")
-                    .param("client_id", testOAuthAppClientId)
-                    .param("client_secret", testOAuthAppClientSecret)
-                    .param("grant_type", "password")
-                    .param("username", user.getUsername())
-                    .param("password", "testpass"))
-            .andExpect(status().isOk())
-            .andReturn();
+    MockHttpServletRequestBuilder accessTokenRequest =
+        post("/oauth/token")
+            .param("client_id", testOAuthAppClientId)
+            .param("client_secret", testOAuthAppClientSecret)
+            .param("grant_type", "password")
+            .param("username", user.getUsername())
+            .param("password", "testpass");
+
+    // get access token
+    MvcResult result = mockMvc.perform(accessTokenRequest).andExpect(status().isOk()).andReturn();
     String jsonResponse = result.getResponse().getContentAsString();
     NewOAuthTokenResponse response = parseOAuthTokenResponse(jsonResponse);
     String token = response.getAccessToken();
 
-    // List root folder tree, this could be any call but testing we are accessing API
+    // list root folder tree, this could be any call but testing we are accessing API
     result =
         mockMvc
             .perform(
@@ -49,5 +51,9 @@ public void createAccessTokenAndAccessApi() throws Exception {
     ApiRecordTreeItemListing folders =
         getFromJsonResponseBody(result, ApiRecordTreeItemListing.class);
     assertTrue(folders.getTotalHits() > 0);
+
+    // confirm that access token request doesn't succeed if oauth authentication is disabled
+    disableApiOAuthAuthentication();
+    mockMvc.perform(accessTokenRequest).andExpect(status().isUnauthorized());
   }
 }
diff --git a/src/test/java/com/researchspace/webapp/controller/OAuthClientCommunityControllerMVCIT.java b/src/test/java/com/researchspace/webapp/controller/OAuthClientCommunityControllerMVCIT.java
index f0189e7047..db2ddf2c15 100644
--- a/src/test/java/com/researchspace/webapp/controller/OAuthClientCommunityControllerMVCIT.java
+++ b/src/test/java/com/researchspace/webapp/controller/OAuthClientCommunityControllerMVCIT.java
@@ -40,6 +40,9 @@ public void googleSignupUserUsesVerificationPassword() throws Exception {
 
     // verification password succeeds
     verificationPasswordHandler.encryptAndSavePassword(user, "abcdefghi");
+
+    // confirm oauth authentication accepts verification password
+    enableApiOAuthAuthentication();
     postOauthAccessTokenRequest(username, password, app).andExpect(status().isOk()).andReturn();
   }
 
diff --git a/src/test/java/com/researchspace/webapp/controller/OAuthClientControllerMVCIT.java b/src/test/java/com/researchspace/webapp/controller/OAuthClientControllerMVCIT.java
index 97fe476866..4b6e38fb88 100644
--- a/src/test/java/com/researchspace/webapp/controller/OAuthClientControllerMVCIT.java
+++ b/src/test/java/com/researchspace/webapp/controller/OAuthClientControllerMVCIT.java
@@ -33,6 +33,7 @@ public void incorrectGrants() throws Exception {
 
     // let's disable API on the server
     disableGlobalApiAccess();
+    disableApiOAuthAuthentication();
 
     // missing parameters (grant_type) are reported first
     mockMvc
@@ -60,8 +61,27 @@ public void incorrectGrants() throws Exception {
                     "Access to API has been disabled by RSpace administrator.",
                     result.getResolvedException().getMessage()));
 
-    // next, report invalid value for parameter grant_type
+    // re-enable global API access
     enableGlobalApiAccess();
+
+    // disabled OAuth authentication is reported next
+    mockMvc
+        .perform(
+            post("/oauth/token")
+                .param("client_id", clientId)
+                .param("client_secret", clientSecret)
+                .param("grant_type", "invalid"))
+        .andExpect(status().isUnauthorized())
+        .andExpect(
+            result ->
+                assertEquals(
+                    "OAuth authentication has been disabled by RSpace administrator.",
+                    result.getResolvedException().getMessage()));
+
+    // re-enable OAuth authentication
+    enableApiOAuthAuthentication();
+
+    // next, report invalid value for parameter grant_type
     mockMvc
         .perform(
             post("/oauth/token")
@@ -188,6 +208,7 @@ public void passwordAndRefreshGrant() throws Exception {
 
     OAuthAppInfo app = oAuthAppManager.addApp(user, "newApp").getEntity();
     enableGlobalApiAccess();
+    enableApiOAuthAuthentication();
 
     MvcResult result =
         mockMvc
@@ -221,8 +242,23 @@ public void passwordAndRefreshGrant() throws Exception {
             .andReturn();
     jsonResponse = result.getResponse().getContentAsString();
     response = parseOAuthTokenResponse(jsonResponse);
-
     assertNotEquals(oldAccessToken, response.getAccessToken());
     assertNotEquals(oldRefreshToken, response.getRefreshToken());
+
+    // confirm that refresh token request won't work after disabling oauth authentication
+    disableApiOAuthAuthentication();
+    mockMvc
+        .perform(
+            post("/oauth/token")
+                .param("client_id", app.getClientId())
+                .param("client_secret", app.getUnhashedClientSecret())
+                .param("grant_type", "refresh_token")
+                .param("refresh_token", response.getRefreshToken()))
+        .andExpect(status().isUnauthorized())
+        .andExpect(
+            res ->
+                assertEquals(
+                    "OAuth authentication has been disabled by RSpace administrator.",
+                    res.getResolvedException().getMessage()));
   }
 }
diff --git a/src/test/java/com/researchspace/webapp/controller/OAuthClientSSOControllerMVCIT.java b/src/test/java/com/researchspace/webapp/controller/OAuthClientSSOControllerMVCIT.java
index a218ce15f4..47c0dc220c 100644
--- a/src/test/java/com/researchspace/webapp/controller/OAuthClientSSOControllerMVCIT.java
+++ b/src/test/java/com/researchspace/webapp/controller/OAuthClientSSOControllerMVCIT.java
@@ -33,6 +33,7 @@ public void ssoSignupUserUsesVerificationPassword() throws Exception {
 
     // if API access is globally disabled, the SSO verification password shouldn't work either
     disableGlobalApiAccess();
+    disableApiOAuthAuthentication();
     OAuthAppInfo app = oAuthAppManager.addApp(user, "newApp").getEntity();
     postOauthAccessTokenRequest(username, password, app)
         .andExpect(status().isUnauthorized())
@@ -44,6 +45,7 @@ public void ssoSignupUserUsesVerificationPassword() throws Exception {
 
     // user1234 now fails
     enableGlobalApiAccess();
+    enableApiOAuthAuthentication();
     app = oAuthAppManager.addApp(user, "newApp").getEntity();
     postOauthAccessTokenRequest(username, password, app)
         .andExpect(status().isUnauthorized())
@@ -57,7 +59,11 @@ public void ssoSignupUserUsesVerificationPassword() throws Exception {
     verificationPasswordhandler.encryptAndSavePassword(user, "abcdefghi");
 
     // verification password succeeds
-    postOauthAccessTokenRequest(username, password, app).andExpect(status().isOk()).andReturn();
+    postOauthAccessTokenRequest(username, password, app).andExpect(status().isOk());
+
+    // disabling just oauth authentication also stops generation of access tokens
+    disableApiOAuthAuthentication();
+    postOauthAccessTokenRequest(username, password, app).andExpect(status().isUnauthorized());
   }
 
   private ResultActions postOauthAccessTokenRequest(


Reviews:
user: login: mKowalski256
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T11:24:49Z
user: login: mKowalski256
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T11:26:04Z
user: login: mKowalski256
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T11:26:46Z
user: login: rs-nicof
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T13:03:15Z
user: login: rs-fraser
body: Looks good.

Tested toggling on/off on the AWS build and works as expected.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T15:58:04Z

Review comments:
diff_hunk: @@ -326,6 +326,8 @@ system.property.description.chemistry.available=Makes chemistry integration avai
   users can create and edit chemical sketches, and search for chemical structures in the Workspace
 system.property.description.slack.available=Makes Slack integration available to the users. After enabling the integration, \
   user can connect to their Slack channels to send messages or forward notifications
+system.property.description.msteams.available=Makes MS Teams integration available to the users. After enabling the integration, \
+  user can connect to their MS Teams channels to send messages or forward notifications
path: src/main/resources/bundles/system/system.properties
user: login: mKowalski256
body: I've noticed that description of a few system configuration settings is missing, hence this and a few more changes to `system.properties` and `settings_ajax.jsp` files, which are not really related to the PR.

created_at: 2025-05-22T11:24:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -350,18 +348,22 @@ system.property.description.argos.available=Makes Argos integration available.
 system.property.description.zenodo.available=Makes Zenodo integration available.
 system.property.description.fieldmark.available=Makes Fieldmark integration available.
 system.property.description.galaxy.available=Makes Galaxy integration available.
-system.property.description.digital_commons_data.available=Makes Digital Commons Data integration available.
 system.property.description.github.available=Makes Github integration available. After enabling, user can add links \
   to files in Github repositories.
+system.property.description.protocols_io.available=Makes Protocols.io integration available, allowing users to create documents from \
+  the protocols available through that service.
 system.property.description.api.available=Enables API access for users
+system.property.description.api.oauth.authentication=Enables accessing public API with OAuth authentication flow
path: src/main/resources/bundles/system/system.properties
user: login: mKowalski256
body: only this line in the file is related to the PR.
created_at: 2025-05-22T11:26:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,15 +79,19 @@
         <div id="fieldmark.available.description"><spring:message code="system.property.description.fieldmark.available" /></div>
         <div id="github.available.description"><spring:message code="system.property.description.github.available" /></div>
         <div id="api.available.description"><spring:message code="system.property.description.api.available" /></div>
+        <div id="api.oauth.authentication.description"><spring:message code="system.property.description.api.oauth.authentication" /></div>
path: src/main/webapp/WEB-INF/pages/system/settings_ajax.jsp
user: login: mKowalski256
body: Only this line in the file is related to the PR.
created_at: 2025-05-22T11:26:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -68,6 +72,14 @@ public NewOAuthTokenResponse getToken(
           "Access to API has been disabled by RSpace administrator.");
     }
 
+    boolean oauthAuthEnabled =
path: src/main/java/com/researchspace/api/v1/controller/OAuthClientController.java
user: login: rs-nicof
body: You could simplify the variable name `oAuthEnabled`
created_at: 2025-05-22T12:56:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -69,6 +69,20 @@ public void enableGlobalApiAccess() {
         SystemPropertyName.API_AVAILABLE, HierarchicalPermission.ALLOWED, getSysAdminUser());
   }
 
+  public void disableApiOauthAuthentication() {
path: src/test/java/com/researchspace/webapp/controller/MVCTestBase.java
user: login: rs-nicof
body: i would suggest to name it with the capital A after the O: `disableApiOAuthAuthentication`
created_at: 2025-05-22T13:01:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -69,6 +69,20 @@ public void enableGlobalApiAccess() {
         SystemPropertyName.API_AVAILABLE, HierarchicalPermission.ALLOWED, getSysAdminUser());
   }
 
+  public void disableApiOauthAuthentication() {
+    sysPropMgr.save(
+        SystemPropertyName.API_OAUTH_AUTHENTICATION,
+        HierarchicalPermission.DENIED,
+        getSysAdminUser());
+  }
+
+  public void enableApiOauthAuthentication() {
path: src/test/java/com/researchspace/webapp/controller/MVCTestBase.java
user: login: rs-nicof
body: i would suggest to name it with the capital A after the O: `enableApiOAuthAuthentication`
created_at: 2025-05-22T13:01:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: mKowalski256
created_at: 2025-05-22T11:27:50Z
body: There is a test build running at https://rsdev-653-oauth-setting-4.researchspace.com/.
author_association: CONTRIBUTOR
user: login: rs-nicof
created_at: 2025-05-22T13:03:04Z
body: I had a first look and the code looks good to me! I've just added a couple of naming change suggestions
author_association: CONTRIBUTOR