Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions libs/wire-api/src/Wire/API/ErrorDescription.hs
Original file line number Diff line number Diff line change
Expand Up @@ -334,3 +334,5 @@ type AssetTooLarge = ErrorDescription 413 "client-error" "Asset too large"
type InvalidLength = ErrorDescription 400 "invalid-length" "Invalid content length"

type AssetNotFound = ErrorDescription 404 "not-found" "Asset not found"

type NameManagedByScim = ErrorDescription 403 "managed-by-scim" "Updating name is not allowed, because it is managed by SCIM"
10 changes: 10 additions & 0 deletions libs/wire-api/src/Wire/API/Routes/Public/Brig.hs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,16 @@ data Api routes = Api
:> "self"
:> ReqBody '[JSON] DeleteUser
:> MultiVerb 'DELETE '[JSON] DeleteSelfResponses (Maybe Timeout),
-- This endpoint can lead to the following events being sent:
-- - UserUpdated event to contacts of self
putSelf ::
routes
:- Summary "Update your profile"
:> ZUser
:> ZConn
:> "self"
:> ReqBody '[JSON] UserUpdate
:> MultiVerb 'PUT '[JSON] PutSelfResponses (Maybe UpdateProfileError),
updateUserEmail ::
routes
:- Summary "Resend email address validation email."
Expand Down
50 changes: 33 additions & 17 deletions libs/wire-api/src/Wire/API/User.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ module Wire.API.User

-- * Profile Updates
UserUpdate (..),
UpdateProfileError (..),
PutSelfResponses,
PasswordChange (..),
LocaleUpdate (..),
EmailUpdate (..),
Expand Down Expand Up @@ -113,9 +115,9 @@ import Data.Json.Util (UTCTimeMillis, (#))
import Data.LegalHold (UserLegalHoldStatus)
import qualified Data.List as List
import Data.Misc (PlainTextPassword (..))
import Data.Proxy (Proxy (..))
import Data.Qualified
import Data.Range
import Data.SOP
import Data.Schema
import qualified Data.Swagger as S
import qualified Data.Swagger.Build.Api as Doc
Expand All @@ -128,7 +130,9 @@ import Imports
import qualified SAML2.WebSSO as SAML
import qualified Test.QuickCheck as QC
import Wire.API.Arbitrary (Arbitrary (arbitrary), GenericUniform (..))
import Wire.API.ErrorDescription
import Wire.API.Provider.Service (ServiceRef, modelServiceRef)
import Wire.API.Routes.MultiVerb
import Wire.API.Team (BindingNewTeam (BindingNewTeam), NewTeam (..), modelNewBindingTeam)
import Wire.API.User.Activation (ActivationCode)
import Wire.API.User.Auth (CookieLabel)
Expand Down Expand Up @@ -830,6 +834,7 @@ data UserUpdate = UserUpdate
uupAccentId :: Maybe ColourId
}
deriving stock (Eq, Show, Generic)
deriving (ToJSON, FromJSON, S.ToSchema) via (Schema UserUpdate)
deriving (Arbitrary) via (GenericUniform UserUpdate)

modelUserUpdate :: Doc.Model
Expand All @@ -845,22 +850,33 @@ modelUserUpdate = Doc.defineModel "UserUpdate" $ do
Doc.description "Accent colour ID"
Doc.optional

instance ToJSON UserUpdate where
toJSON u =
A.object $
"name" A..= uupName u
# "picture" A..= uupPict u
# "assets" A..= uupAssets u
# "accent_id" A..= uupAccentId u
# []

instance FromJSON UserUpdate where
parseJSON = A.withObject "UserUpdate" $ \o ->
UserUpdate
<$> o A..:? "name"
<*> o A..:? "picture"
<*> o A..:? "assets"
<*> o A..:? "accent_id"
instance ToSchema UserUpdate where
schema =
object "UserUpdate" $
UserUpdate
<$> uupName .= maybe_ (optField "name" schema)
<*> uupPict .= maybe_ (optField "picture" schema)
<*> uupAssets .= maybe_ (optField "assets" (array schema))
<*> uupAccentId .= maybe_ (optField "accent_id" schema)

data UpdateProfileError
= DisplayNameManagedByScim
| ProfileNotFound

type PutSelfResponses =
'[ UserNotFound,
NameManagedByScim,
RespondEmpty 200 "User updated"
]

instance AsUnion PutSelfResponses (Maybe UpdateProfileError) where
toUnion (Just ProfileNotFound) = Z (I mkErrorDescription)
toUnion (Just DisplayNameManagedByScim) = S (Z (I mkErrorDescription))
toUnion Nothing = S (S (Z (I ())))
fromUnion (Z (I _)) = Just ProfileNotFound
fromUnion (S (Z (I _))) = Just DisplayNameManagedByScim
fromUnion (S (S (Z (I _)))) = Nothing
fromUnion (S (S (S x))) = case x of

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be probably easy to automate using GenericAsUnion.

You could also consider creating a type like UpdateProfileResult with 3 constructors instead, and then you could directly use deriving via to get the AsUnion instance.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the tip. I created maybeToUnion and maybeFromUnion (this one uses eitherFromUnion) to define these instances. Let me know if they look ok.


-- | The payload for setting or changing a password.
data PasswordChange = PasswordChange
Expand Down
3 changes: 2 additions & 1 deletion services/brig/src/Brig/API/Error.hs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import qualified Network.Wai.Utilities.Error as Wai
import Servant.API.Status
import Wire.API.ErrorDescription
import Wire.API.Federation.Error
import Wire.API.User (UpdateProfileError (..))

errorDescriptionToWai ::
forall (code :: Nat) (lbl :: Symbol) (desc :: Symbol).
Expand Down Expand Up @@ -262,7 +263,7 @@ phoneError (PhoneBudgetExhausted t) = RichError phoneBudgetExhausted (PhoneBudge

updateProfileError :: UpdateProfileError -> Error
updateProfileError DisplayNameManagedByScim = StdError (propertyManagedByScim "name")
updateProfileError (ProfileNotFound _) = StdError (errorDescriptionTypeToWai @UserNotFound)
updateProfileError ProfileNotFound = StdError (errorDescriptionTypeToWai @UserNotFound)

-- WAI Errors -----------------------------------------------------------------

Expand Down
22 changes: 5 additions & 17 deletions services/brig/src/Brig/API/Public.hs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ servantSitemap =
BrigAPI.getUserQualified = getUser,
BrigAPI.getSelf = getSelf,
BrigAPI.deleteSelf = deleteUser,
BrigAPI.putSelf = updateUser,
BrigAPI.updateUserEmail = updateUserEmail,
BrigAPI.getHandleInfoUnqualified = getHandleInfoUnqualifiedH,
BrigAPI.getUserByHandleQualified = Handle.getHandleInfo,
Expand Down Expand Up @@ -255,18 +256,6 @@ sitemap = do

-- User Self API ------------------------------------------------------

-- This endpoint can lead to the following events being sent:
-- - UserUpdated event to contacts of self
put "/self" (continue updateUserH) $
zauthUserId
.&. zauthConnId
.&. jsonRequest @Public.UserUpdate
document "PUT" "updateSelf" $ do
Doc.summary "Update your profile"
Doc.body (Doc.ref Public.modelUserUpdate) $
Doc.description "JSON body"
Doc.response 200 "Update successful." Doc.end

get "/self/name" (continue getUserDisplayNameH) $
accept "application" "json"
.&. zauthUserId
Expand Down Expand Up @@ -894,11 +883,10 @@ newtype GetActivationCodeResp
instance ToJSON GetActivationCodeResp where
toJSON (GetActivationCodeResp (k, c)) = object ["key" .= k, "code" .= c]

updateUserH :: UserId ::: ConnId ::: JsonRequest Public.UserUpdate -> Handler Response
updateUserH (uid ::: conn ::: req) = do
uu <- parseJsonBody req
API.updateUser uid (Just conn) uu API.ForbidSCIMUpdates !>> updateProfileError
return empty
updateUser :: UserId -> ConnId -> Public.UserUpdate -> Handler (Maybe Public.UpdateProfileError)
updateUser uid conn uu = do
eithErr <- lift $ runExceptT $ API.updateUser uid (Just conn) uu API.ForbidSCIMUpdates
pure $ either Just (const Nothing) eithErr

changePhoneH :: UserId ::: ConnId ::: JsonRequest Public.PhoneUpdate -> Handler Response
changePhoneH (u ::: c ::: req) =
Expand Down
4 changes: 0 additions & 4 deletions services/brig/src/Brig/API/Types.hs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ data CreateUserError
| -- | Some precondition on another Wire service failed. We propagate this error.
ExternalPreconditionFailed Wai.Error

data UpdateProfileError
= DisplayNameManagedByScim
| ProfileNotFound UserId

data InvitationError
= InviteeEmailExists UserId
| InviteInvalidEmail Email
Expand Down
3 changes: 2 additions & 1 deletion services/brig/src/Brig/API/User.hs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ import UnliftIO.Async
import Wire.API.Federation.Error
import Wire.API.Routes.Internal.Brig.Connection
import Wire.API.Team.Member (legalHoldStatus)
import Wire.API.User (UpdateProfileError (..))

data AllowSCIMUpdates
= AllowSCIMUpdates
Expand Down Expand Up @@ -454,7 +455,7 @@ updateUser :: UserId -> Maybe ConnId -> UserUpdate -> AllowSCIMUpdates -> Except
updateUser uid mconn uu allowScim = do
for_ (uupName uu) $ \newName -> do
mbUser <- lift $ Data.lookupUser WithPendingInvitations uid
user <- maybe (throwE (ProfileNotFound uid)) pure mbUser
user <- maybe (throwE ProfileNotFound) pure mbUser
unless
( userManagedBy user /= ManagedByScim
|| userDisplayName user == newName
Expand Down