Skip to content

Commit 248cc50

Browse files
authored
Merge pull request #784 from wenyang-cao/fix/oauth-behavior
block some operations in oauth mode
2 parents 0e33f3b + dfe81b6 commit 248cc50

12 files changed

Lines changed: 1298 additions & 622 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [2.136.0](https://github.com/open-horizon/exchange-api/pull/784) - 2025-07-09
8+
- Issue 782: Disable password update for external users in OAuth mode. Affects Put and Patch User routes, and the change-password route.
9+
- Issue 783: Disable user creation via PUT endpoint in OAuth mode
10+
711
## [2.135.0](https://github.com/open-horizon/exchange-api/pull/788) - 2025-07-07
812
- Issue 787: Added /users/apikey and /users/iamapikey endpoints to let clients retrieve their own user info when using API key authentication without explicitly setting the username (e.g. to support how hzn and other components construct requests)
913

src/main/resources/exchange.conf

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ api {
5353
tls.password = ${?EXCHANGE_TLS_PASSWORD}
5454
tls.truststore = ${?api.tls.truststore}
5555
tls.truststore = ${?EXCHANGE_TLS_TRUSTSTORE}
56-
57-
oauth.enabled = ${?EXCHANGE_OAUTH_ENABLED}
5856
}
5957

6058
exchange-db-connection {

src/main/resources/messages.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ over.max.limit.of.nodes=Access Denied: you are over the limit of {0} nodes
300300
over.org.max.limit.of.nodes=Access Denied: Your current total number of nodes, {0}, is either equal to or over the org limit of {1} nodes
301301
over.the.limit.of.services=Access Denied: you are over the limit of {0} services
302302
password.cannot.be.set.to.empty.string=the password cannot be set to the empty string
303+
password.disabled.oauth.user=Password operations are not allowed for external OAuth users
303304
password.must.be.non.blank.when.creating.user=the password must be non-blank when creating a user
304305
password.updated.successfully=password updated successfully
305306
pattern.added=pattern {0} added
@@ -388,8 +389,10 @@ unknown.error.invalid.creds=unknown error or invalid credentials
388389
updated=updated
389390
user.added.or.updated.successfully={0} user added or updated successfully
390391
user.added.successfully={0} user added successfully
392+
user.attr.not.allowed.oauth=This user attribute cannot be modified in OAuth mode
391393
user.attr.updated=attribute ''{0}'' of user ''{1}'' updated
392394
user.cannot.be.in.root.org=Only the root user and hub admins can exist in the root org
395+
user.creation.disabled.oauth=User creation is disabled in OAuth mode. Use external identity provider to manage users.
393396
user.deleted=user deleted
394397
user.not.added.or.updated.successfully=user not added or updated: {0}
395398
user.not.added=user not added: {0}

src/main/resources/reference.conf

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,6 @@ api {
159159
tls.password = null # Truststore's password
160160
tls.truststore = null # Absolute path and name of your pkcs12 (.p12) truststore that contains your tls certificate and private key
161161

162-
oauth.enabled = false
163-
164162
cache {
165163
authDbTimeoutSeconds = 15 # Timeout for db access for critical auth info when cache missing
166164
IAMusersMaxSize = 300 # The users that are backed by IAM users

src/main/resources/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.135.0
1+
2.136.0

src/main/scala/org/openhorizon/exchangeapi/route/user/ChangePassword.scala

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,9 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
9999

100100
validateWithMsg(reqBody.getAnyProblem) {
101101
val timestamp: java.sql.Timestamp = ApiTime.nowUTCTimestamp
102+
val isOAuthEnabled = Configuration.getConfig.hasPath("api.authentication.oauth.provider.user_info.url")
102103

103104
val action =
104-
for {
105-
numUsersModified <-
106105
Compiled(UsersTQ.filter(user => (user.organization === organization &&
107106
user.username === username))
108107
.filterIf(identity.isUser && !identity.isSuperUser)(users => (users.organization ++ "/" ++ users.username) =!= "root/root")
@@ -116,10 +115,33 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
116115
.update(timestamp,
117116
identity.identifier,
118117
Option(Password.hash(reqBody.newPassword))) // Grab this last second.
119-
} yield numUsersModified
118+
119+
val checkExternalUserQuery = Compiled(
120+
UsersTQ.filter(user => user.organization === organization && user.username === username)
121+
.filter(_.identityProvider =!= "Open Horizon")
122+
.take(1)
123+
.length
124+
)
125+
126+
val checkUserAndUpdate: DBIOAction[Int, NoStream, Effect.Read with Effect.Write] =
127+
if (isOAuthEnabled) {
128+
for {
129+
externalUserCount <- checkExternalUserQuery.result
130+
131+
_ <- if (externalUserCount > 0) {
132+
DBIO.failed(new MethodNotAllowedException(ExchMsg.translate("password.disabled.oauth.user")))
133+
} else {
134+
DBIO.successful(())
135+
}
136+
137+
numUsersModified <- action
138+
} yield numUsersModified
139+
} else {
140+
action
141+
}
120142

121143
complete {
122-
db.run(action.transactionally.asTry).map {
144+
db.run(checkUserAndUpdate.transactionally.asTry).map {
123145
case Success(numUsersModified) =>
124146
Future { logger.debug("POST /orgs/" + organization + "/users/" + username + "/changepw result: " + numUsersModified)
125147
}
@@ -135,6 +157,8 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
135157
}
136158
else
137159
(HttpCode.NOT_FOUND, ApiResponse(ApiRespType.NOT_FOUND, ExchMsg.translate("user.not.found", resource)))
160+
case Failure(t: MethodNotAllowedException) =>
161+
(HttpCode.NOT_ALLOWED, ApiResponse(ApiRespType.METHOD_NOT_ALLOWED, ExchMsg.translate("password.disabled.oauth.user")))
138162
case Failure(t: org.postgresql.util.PSQLException) =>
139163
ExchangePosgtresErrorHandling.ioProblemError(t, ExchMsg.translate("user.password.not.updated", resource, t.toString))
140164
case Failure(t) =>
@@ -143,7 +167,7 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
143167
}
144168
}
145169
}
146-
170+
147171
def changePassword(identity: Identity2): Route =
148172
path("orgs" / Segment / "users" / Segment / "changepw") {
149173
(organization, username) =>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.openhorizon.exchangeapi.route.user
2+
3+
import org.openhorizon.exchangeapi.utility.ExchMsg
4+
5+
final case class MethodNotAllowedException(override val getMessage: String)
6+
extends RuntimeException(getMessage)

0 commit comments

Comments
 (0)