17
17
package uk .gov .hmrc .cataloguefrontend .users
18
18
19
19
import play .api .Logging
20
- import play .api .data .validation .Constraints
20
+ import play .api .data .validation .{ Constraint , Constraints , Invalid , Valid }
21
21
import play .api .data .{Form , Forms }
22
22
import play .api .mvc .*
23
23
import play .twirl .api .Html
@@ -54,10 +54,11 @@ class UsersController @Inject()(
54
54
with Logging :
55
55
56
56
private def showUserInfoPage (
57
- username : UserName ,
58
- retrieval : Option [Set [Resource ]],
59
- resultType : Html => Result ,
60
- form : Form [_]
57
+ username : UserName ,
58
+ retrieval : Option [Set [Resource ]],
59
+ resultType : Html => Result ,
60
+ ldapForm : Form [ResetLdapPassword ],
61
+ userDetailsForm : Form [EditUserDetailsRequest ]
61
62
)(using HeaderCarrier , RequestHeader ): Future [Result ] =
62
63
for
63
64
userOpt <- userManagementConnector.getUser(username)
@@ -70,7 +71,7 @@ class UsersController @Inject()(
70
71
userOpt match
71
72
case Some (user) =>
72
73
val umpProfileUrl = s " ${umpConfig.userManagementProfileBaseUrl}/ ${user.username.asString}"
73
- resultType(userInfoPage(form , isAdminForUser(retrieval, user), userTooling, user, umpProfileUrl))
74
+ resultType(userInfoPage(ldapForm, userDetailsForm , isAdminForUser(retrieval, user), userTooling, user, umpProfileUrl))
74
75
case None =>
75
76
NotFound (error_404_template())
76
77
@@ -82,9 +83,32 @@ class UsersController @Inject()(
82
83
resourceType = Some (ResourceType (" catalogue-frontend" )),
83
84
action = Some (IAAction (" EDIT_USER" ))
84
85
))
85
- result <- showUserInfoPage(username, retrieval, Ok (_), LdapResetForm .form)
86
+ result <- showUserInfoPage(username, retrieval, Ok (_), LdapResetForm .form, EditUserDetailsForm .form )
86
87
yield result
87
88
89
+ def updateUserDetails (username : UserName ): Action [AnyContent ] =
90
+ BasicAuthAction .async: request =>
91
+ given MessagesRequest [AnyContent ] = request
92
+ EditUserDetailsForm .form.bindFromRequest().fold(
93
+ formWithErrors =>
94
+ for
95
+ retrieval <- auth.verify(Retrieval .locations(
96
+ resourceType = Some (ResourceType (" catalogue-frontend" )),
97
+ action = Some (IAAction (" EDIT_USER" ))
98
+ ))
99
+ result <- showUserInfoPage(username, retrieval, BadRequest (_), LdapResetForm .form, formWithErrors)
100
+ yield result
101
+ , editRequest =>
102
+ userManagementConnector.editUserDetails(editRequest)
103
+ .map: _ =>
104
+ Redirect (routes.UsersController .user(UserName (editRequest.username)))
105
+ .flashing(" success" -> s " User's ${editRequest.attribute.description} has been updated successfully " )
106
+ .recover:
107
+ case NonFatal (e) =>
108
+ Redirect (routes.UsersController .user(UserName (editRequest.username)))
109
+ .flashing(s " error " -> s " Error updating User Details for user's ${editRequest.attribute.description}. Contact #team-platops " )
110
+ )
111
+
88
112
def requestLdapReset (username : UserName ): Action [AnyContent ] =
89
113
auth.authenticatedAction(
90
114
continueUrl = routes.UsersController .user(username),
@@ -93,8 +117,8 @@ class UsersController @Inject()(
93
117
given AuthenticatedRequest [AnyContent , Set [Resource ]] = request
94
118
LdapResetForm .form.bindFromRequest().fold(
95
119
formWithErrors =>
96
- showUserInfoPage(username, Some (request.retrieval), BadRequest (_), formWithErrors)
97
- , formData =>
120
+ showUserInfoPage(username, Some (request.retrieval), BadRequest (_), formWithErrors, EditUserDetailsForm .form )
121
+ , formData =>
98
122
userManagementConnector.resetLdapPassword(formData).map: ticketOpt =>
99
123
Ok (ldapResetRequestSentPage(username, ticketOpt))
100
124
.recover:
@@ -162,3 +186,79 @@ object LdapResetForm:
162
186
" email" -> Forms .text.verifying(Constraints .emailAddress(errorMessage = " Please provide a valid email address." ))
163
187
)(ResetLdapPassword .apply)(f => Some (Tuple .fromProductTyped(f)))
164
188
)
189
+
190
+ object EditUserDetailsForm :
191
+ val form : Form [EditUserDetailsRequest ] = Form (
192
+ Forms .mapping(
193
+ " username" -> Forms .nonEmptyText,
194
+ " attribute" -> Forms .nonEmptyText.transform[UserAttribute ](UserAttribute .fromString(_).get, _.name),
195
+ " displayName" -> Forms .optional(Forms .text),
196
+ " phoneNumber" -> Forms .optional(Forms .text),
197
+ " github" -> Forms .optional(Forms .text),
198
+ " organisation" -> Forms .optional(Forms .text)
199
+ ) { (username, attribute, displayNameOpt, phoneNumberOpt, githubOpt, organisationOpt) =>
200
+ val value = attribute match
201
+ case UserAttribute .DisplayName => displayNameOpt.getOrElse(" " )
202
+ case UserAttribute .PhoneNumber => phoneNumberOpt.getOrElse(" " )
203
+ case UserAttribute .Github => githubOpt.getOrElse(" " )
204
+ case UserAttribute .Organisation => organisationOpt.getOrElse(" " )
205
+ EditUserDetailsRequest (username, attribute, value)
206
+ } { editUserDetailsRequest =>
207
+ Some ((
208
+ editUserDetailsRequest.username,
209
+ editUserDetailsRequest.attribute,
210
+ if editUserDetailsRequest.attribute == UserAttribute .DisplayName then Some (editUserDetailsRequest.value) else None ,
211
+ if editUserDetailsRequest.attribute == UserAttribute .PhoneNumber then Some (editUserDetailsRequest.value) else None ,
212
+ if editUserDetailsRequest.attribute == UserAttribute .Github then Some (editUserDetailsRequest.value) else None ,
213
+ if editUserDetailsRequest.attribute == UserAttribute .Organisation then Some (editUserDetailsRequest.value) else None
214
+ ))
215
+ }.verifying(validateByAttribute)
216
+ )
217
+
218
+ private def mkConstraint [T ](constraintName : String )(constraint : T => Boolean , error : String ): Constraint [T ] =
219
+ Constraint (constraintName): toBeValidated =>
220
+ if constraint(toBeValidated) then Valid else Invalid (error)
221
+
222
+ private val nameConstraints : Constraint [String ] =
223
+ val nameLengthValidation : String => Boolean = str => str.length >= 2 && str.length <= 30
224
+
225
+ mkConstraint(s " constraints.displayNameLengthCheck " )(
226
+ constraint = nameLengthValidation,
227
+ error = " Name should be between 2 and 30 characters long"
228
+ )
229
+
230
+ private val phoneNumberConstraint : Constraint [String ] =
231
+ val phoneNumberValidation : String => Boolean =
232
+ _.matches(""" ^(?=.*\d)[\d\s/+]*$""" )
233
+
234
+ mkConstraint(" constraints.phoneNumber" )(
235
+ constraint = phoneNumberValidation,
236
+ error = " Phone number can only contain digits, spaces, plus signs, or slashes."
237
+ )
238
+
239
+ private val githubUsernameConstraint : Constraint [String ] =
240
+ val githubUsernameValidation : String => Boolean =
241
+ ! _.isBlank
242
+
243
+ mkConstraint(" constraints.githubUsername" )(
244
+ constraint = githubUsernameValidation,
245
+ error = " GitHub username cannot be set to empty once it has been provided."
246
+ )
247
+
248
+ private val organisationConstraint : Constraint [String ] =
249
+ val organisationValidation : String => Boolean =
250
+ Organisation .values.map(_.asString).contains(_)
251
+
252
+ mkConstraint(" constraints.organisation" )(
253
+ constraint = organisationValidation,
254
+ error = " Organisation must be MDTP, VOA, or Other."
255
+ )
256
+
257
+ private def validateByAttribute : Constraint [EditUserDetailsRequest ] = Constraint (" constraints.editUserDetailsRequest" ) { editUserDetailsRequest =>
258
+ editUserDetailsRequest.attribute match
259
+ case UserAttribute .DisplayName => nameConstraints(editUserDetailsRequest.value)
260
+ case UserAttribute .PhoneNumber => phoneNumberConstraint(editUserDetailsRequest.value)
261
+ case UserAttribute .Github => githubUsernameConstraint(editUserDetailsRequest.value)
262
+ case UserAttribute .Organisation => organisationConstraint(editUserDetailsRequest.value)
263
+ }
264
+
0 commit comments