Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

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

## [2.136.0](https://github.com/open-horizon/exchange-api/pull/784) - 2025-07-09
- Issue 782: Disable password update for external users in OAuth mode. Affects Put and Patch User routes, and the change-password route.
- Issue 783: Disable user creation via PUT endpoint in OAuth mode

## [2.135.0](https://github.com/open-horizon/exchange-api/pull/788) - 2025-07-07
- 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)

Expand Down
2 changes: 0 additions & 2 deletions src/main/resources/exchange.conf
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,6 @@ api {
tls.password = ${?EXCHANGE_TLS_PASSWORD}
tls.truststore = ${?api.tls.truststore}
tls.truststore = ${?EXCHANGE_TLS_TRUSTSTORE}

oauth.enabled = ${?EXCHANGE_OAUTH_ENABLED}
}

exchange-db-connection {
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/messages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ over.max.limit.of.nodes=Access Denied: you are over the limit of {0} nodes
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
over.the.limit.of.services=Access Denied: you are over the limit of {0} services
password.cannot.be.set.to.empty.string=the password cannot be set to the empty string
password.disabled.oauth.user=Password operations are not allowed for external OAuth users
password.must.be.non.blank.when.creating.user=the password must be non-blank when creating a user
password.updated.successfully=password updated successfully
pattern.added=pattern {0} added
Expand Down Expand Up @@ -388,8 +389,10 @@ unknown.error.invalid.creds=unknown error or invalid credentials
updated=updated
user.added.or.updated.successfully={0} user added or updated successfully
user.added.successfully={0} user added successfully
user.attr.not.allowed.oauth=This user attribute cannot be modified in OAuth mode
user.attr.updated=attribute ''{0}'' of user ''{1}'' updated
user.cannot.be.in.root.org=Only the root user and hub admins can exist in the root org
user.creation.disabled.oauth=User creation is disabled in OAuth mode. Use external identity provider to manage users.
user.deleted=user deleted
user.not.added.or.updated.successfully=user not added or updated: {0}
user.not.added=user not added: {0}
Expand Down
2 changes: 0 additions & 2 deletions src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,6 @@ api {
tls.password = null # Truststore's password
tls.truststore = null # Absolute path and name of your pkcs12 (.p12) truststore that contains your tls certificate and private key

oauth.enabled = false

cache {
authDbTimeoutSeconds = 15 # Timeout for db access for critical auth info when cache missing
IAMusersMaxSize = 300 # The users that are backed by IAM users
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.135.0
2.136.0
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,9 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {

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

val action =
for {
numUsersModified <-
Compiled(UsersTQ.filter(user => (user.organization === organization &&
user.username === username))
.filterIf(identity.isUser && !identity.isSuperUser)(users => (users.organization ++ "/" ++ users.username) =!= "root/root")
Expand All @@ -116,10 +115,33 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
.update(timestamp,
identity.identifier,
Option(Password.hash(reqBody.newPassword))) // Grab this last second.
} yield numUsersModified

val checkExternalUserQuery = Compiled(
UsersTQ.filter(user => user.organization === organization && user.username === username)
.filter(_.identityProvider =!= "Open Horizon")
.take(1)
.length
)

val checkUserAndUpdate: DBIOAction[Int, NoStream, Effect.Read with Effect.Write] =
if (isOAuthEnabled) {
for {
externalUserCount <- checkExternalUserQuery.result

_ <- if (externalUserCount > 0) {
DBIO.failed(new MethodNotAllowedException(ExchMsg.translate("password.disabled.oauth.user")))
} else {
DBIO.successful(())
}

numUsersModified <- action
} yield numUsersModified
} else {
action
}

complete {
db.run(action.transactionally.asTry).map {
db.run(checkUserAndUpdate.transactionally.asTry).map {
case Success(numUsersModified) =>
Future { logger.debug("POST /orgs/" + organization + "/users/" + username + "/changepw result: " + numUsersModified)
}
Expand All @@ -135,6 +157,8 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
}
else
(HttpCode.NOT_FOUND, ApiResponse(ApiRespType.NOT_FOUND, ExchMsg.translate("user.not.found", resource)))
case Failure(t: MethodNotAllowedException) =>
(HttpCode.NOT_ALLOWED, ApiResponse(ApiRespType.METHOD_NOT_ALLOWED, ExchMsg.translate("password.disabled.oauth.user")))
case Failure(t: org.postgresql.util.PSQLException) =>
ExchangePosgtresErrorHandling.ioProblemError(t, ExchMsg.translate("user.password.not.updated", resource, t.toString))
case Failure(t) =>
Expand All @@ -143,7 +167,7 @@ trait ChangePassword extends JacksonSupport with AuthenticationSupport {
}
}
}

def changePassword(identity: Identity2): Route =
path("orgs" / Segment / "users" / Segment / "changepw") {
(organization, username) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.openhorizon.exchangeapi.route.user

import org.openhorizon.exchangeapi.utility.ExchMsg

final case class MethodNotAllowedException(override val getMessage: String)
extends RuntimeException(getMessage)
Loading