Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Samuel Dudík
PA165 Winery Management System
Commits
1c421be9
Commit
1c421be9
authored
May 14, 2022
by
Ondřej Pavlica
Browse files
Merge branch 'feature/balga/userRoles' into 'main'
Feature/balga/user roles See merge request
!36
parents
2ce9437a
21c1f6c3
Pipeline
#139043
passed with stages
in 2 minutes and 42 seconds
Changes
10
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
winery/api/src/main/java/cz/muni/fi/pa165/winery/dto/user/UserDto.java
View file @
1c421be9
...
...
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.stream.Collectors
;
/**
* @author Samuel Dudík
...
...
@@ -59,4 +60,8 @@ public class UserDto extends PersistentDtoBase {
public
boolean
hasRole
(
String
roleName
)
{
return
roles
.
stream
().
filter
(
x
->
x
.
name
().
equals
(
roleName
)).
findFirst
().
isPresent
();
}
public
List
<
UserRoleDto
>
getRoleDtos
()
{
return
roles
.
stream
().
map
(
x
->
UserRoleDto
.
builder
().
userId
(
getId
()).
role
(
x
).
build
()).
collect
(
Collectors
.
toList
());
}
}
winery/api/src/main/java/cz/muni/fi/pa165/winery/services/user/UserRoleService.java
View file @
1c421be9
...
...
@@ -16,4 +16,11 @@ public interface UserRoleService extends PersistenceService<UserRoleDto> {
* @return List of user roles.
*/
List
<
UserRoleDto
>
getAll
(
long
userId
);
/**
* Set the user's roles.
* @param userId ID of the given user.
* @param roles The roles
*/
void
setRoles
(
long
userId
,
List
<
UserRoleDto
>
roles
);
}
winery/service/src/main/java/cz/muni/fi/pa165/winery/services/user/UserRoleServiceImpl.java
View file @
1c421be9
...
...
@@ -29,4 +29,19 @@ public class UserRoleServiceImpl extends PersistenceServiceImpl<UserRoleDto, Use
var
entities
=
((
UserRoleDao
)
getDao
()).
getAll
(
userId
);
return
entities
.
stream
().
map
(
this
::
mapToDto
).
collect
(
Collectors
.
toList
());
}
@Override
public
void
setRoles
(
long
userId
,
List
<
UserRoleDto
>
roles
)
{
var
currentRoles
=
getAll
(
userId
);
var
toDelete
=
currentRoles
.
stream
().
filter
(
x
->
!
roles
.
contains
(
x
)).
collect
(
Collectors
.
toList
());
var
toAdd
=
roles
.
stream
().
filter
(
x
->
!
currentRoles
.
contains
(
x
)).
collect
(
Collectors
.
toList
());
for
(
var
role
:
toDelete
)
{
delete
(
role
);
}
for
(
var
role
:
toAdd
)
{
create
(
role
);
}
}
}
winery/service/src/test/java/cz/muni/fi/pa165/winery/services/persistence/user/UserRoleTests.java
View file @
1c421be9
...
...
@@ -22,8 +22,11 @@ import org.mockito.Spy;
import
org.mockito.junit.jupiter.MockitoExtension
;
import
java.util.List
;
import
java.util.stream.Collector
;
import
java.util.stream.Collectors
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
@ExtendWith
(
MockitoExtension
.
class
)
public
class
UserRoleTests
extends
PersistenceServiceTestBase
<
UserRoleDto
,
UserRole
>
{
...
...
@@ -47,6 +50,20 @@ public class UserRoleTests extends PersistenceServiceTestBase<UserRoleDto, UserR
Mockito
.
verify
(
userRoleDao
,
Mockito
.
times
(
1
)).
getAll
(
1
);
}
@Test
public
void
testSetRoles
()
{
getPersistenceService
();
var
entityList
=
List
.
of
(
getTestEntity
());
Mockito
.
when
(
userRoleDao
.
getAll
(
1L
)).
thenReturn
(
entityList
);
var
newRoles
=
List
.
of
((
UserRoleDto
)
UserRoleDto
.
builder
()
.
role
(
UserRoleType
.
ADMIN
)
.
userId
(
1
)
.
build
());
userRoleService
.
setRoles
(
1
,
newRoles
);
Mockito
.
verify
(
userRoleDao
,
Mockito
.
times
(
1
)).
delete
(
any
(
UserRole
.
class
));
Mockito
.
verify
(
userRoleDao
,
Mockito
.
times
(
1
)).
create
(
any
(
UserRole
.
class
));
}
@Override
protected
UserRoleDto
getTestDto
()
{
var
dto
=
new
UserRoleDto
();
...
...
winery/webapp/src/main/java/cz/muni/fi/pa165/winery/webapp/controllers/UserController.java
View file @
1c421be9
package
cz.muni.fi.pa165.winery.webapp.controllers
;
import
cz.muni.fi.pa165.winery.dto.user.UserDto
;
import
cz.muni.fi.pa165.winery.services.user.UserRoleService
;
import
cz.muni.fi.pa165.winery.services.user.UserService
;
import
cz.muni.fi.pa165.winery.webapp.models.user.UserInsertViewModel
;
import
cz.muni.fi.pa165.winery.webapp.models.user.UserListingViewModel
;
import
cz.muni.fi.pa165.winery.webapp.models.user.UserUp
sert
ViewModel
;
import
cz.muni.fi.pa165.winery.webapp.models.user.UserUp
date
ViewModel
;
import
org.springframework.security.access.annotation.Secured
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.stereotype.Controller
;
import
org.springframework.validation.BindingResult
;
import
org.springframework.web.bind.annotation.*
;
...
...
@@ -20,9 +23,13 @@ import java.util.ArrayList;
@Controller
public
class
UserController
extends
ControllerBase
{
private
final
UserService
userService
;
private
final
UserRoleService
userRoleService
;
private
final
PasswordEncoder
encoder
;
public
UserController
(
UserService
userService
)
{
public
UserController
(
UserService
userService
,
UserRoleService
userRoleService
,
PasswordEncoder
encoder
)
{
this
.
userService
=
userService
;
this
.
userRoleService
=
userRoleService
;
this
.
encoder
=
encoder
;
}
@GetMapping
(
""
)
...
...
@@ -46,7 +53,7 @@ public class UserController extends ControllerBase {
return
notFound
();
}
var
viewModel
=
new
UserUp
sert
ViewModel
(
user
);
var
viewModel
=
new
UserUp
date
ViewModel
(
user
);
initializeViewModel
(
viewModel
);
return
view
(
viewModel
);
...
...
@@ -54,13 +61,17 @@ public class UserController extends ControllerBase {
@PostMapping
(
"/edit"
)
@Secured
(
"ROLE_ADMIN"
)
public
ModelAndView
edit
(
@Valid
@ModelAttribute
(
"model"
)
UserUp
sert
ViewModel
viewModel
,
BindingResult
bindingResult
)
{
public
ModelAndView
edit
(
@Valid
@ModelAttribute
(
"model"
)
UserUp
date
ViewModel
viewModel
,
BindingResult
bindingResult
)
{
if
(
bindingResult
.
hasErrors
())
{
return
view
(
bindingResult
.
getModel
());
}
var
dto
=
viewModel
.
toDto
();
var
dto
=
viewModel
.
toDto
(
encoder
);
if
(
viewModel
.
getPassword
().
isEmpty
())
{
dto
.
setPasswordHash
(
userService
.
get
(
dto
.
getId
()).
getPasswordHash
());
}
userService
.
update
(
dto
);
userRoleService
.
setRoles
(
dto
.
getId
(),
dto
.
getRoleDtos
());
initializeViewModel
(
viewModel
);
viewModel
.
setSuccess
(
true
);
...
...
@@ -71,7 +82,7 @@ public class UserController extends ControllerBase {
@GetMapping
(
"/add"
)
@Secured
(
"ROLE_ADMIN"
)
public
ModelAndView
add
()
{
var
viewModel
=
new
User
Up
sertViewModel
();
var
viewModel
=
new
User
In
sertViewModel
();
initializeViewModel
(
viewModel
);
return
view
(
viewModel
);
...
...
@@ -79,13 +90,14 @@ public class UserController extends ControllerBase {
@PostMapping
(
"/add"
)
@Secured
(
"ROLE_ADMIN"
)
public
ModelAndView
add
(
@Valid
@ModelAttribute
(
"model"
)
User
Up
sertViewModel
viewModel
,
BindingResult
bindingResult
)
{
public
ModelAndView
add
(
@Valid
@ModelAttribute
(
"model"
)
User
In
sertViewModel
viewModel
,
BindingResult
bindingResult
)
{
if
(
bindingResult
.
hasErrors
())
{
return
view
(
bindingResult
.
getModel
());
}
var
dto
=
viewModel
.
toDto
();
var
dto
=
viewModel
.
toDto
(
encoder
);
userService
.
create
(
dto
);
userRoleService
.
setRoles
(
dto
.
getId
(),
dto
.
getRoleDtos
());
initializeViewModel
(
viewModel
);
viewModel
.
setSuccess
(
true
);
...
...
winery/webapp/src/main/java/cz/muni/fi/pa165/winery/webapp/models/user/User
Up
sertViewModel.java
→
winery/webapp/src/main/java/cz/muni/fi/pa165/winery/webapp/models/user/User
In
sertViewModel.java
View file @
1c421be9
...
...
@@ -7,12 +7,11 @@ import lombok.Getter;
import
lombok.NoArgsConstructor
;
import
lombok.Setter
;
import
org.modelmapper.ModelMapper
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
javax.validation.constraints.Email
;
import
javax.validation.constraints.NotBlank
;
import
javax.validation.constraints.NotNull
;
import
java.util.ArrayList
;
import
java.util.List
;
/**
* @author Jakub Balga
...
...
@@ -20,16 +19,13 @@ import java.util.List;
@Getter
@Setter
@NoArgsConstructor
public
class
UserUpsertViewModel
extends
ViewModelBase
{
public
UserUpsertViewModel
(
UserDto
dto
)
{
var
mapper
=
new
ModelMapper
();
mapper
.
map
(
dto
,
this
);
}
public
class
UserInsertViewModel
extends
ViewModelBase
{
private
long
id
;
private
boolean
success
;
private
boolean
regular
;
private
boolean
admin
;
/**
* User's first name (forename).
...
...
@@ -51,20 +47,23 @@ public class UserUpsertViewModel extends ViewModelBase {
private
String
email
;
/**
* The
hash of the
user submitted password
* The user submitted password
*
*
Used for authentication
*
Not stored directly
*/
@NotBlank
private
String
passwordHash
;
/**
* Roles assigned to this user
*/
private
List
<
UserRoleType
>
roles
=
new
ArrayList
<>();
private
String
password
;
public
UserDto
toDto
()
{
public
UserDto
toDto
(
PasswordEncoder
encoder
)
{
var
mapper
=
new
ModelMapper
();
return
mapper
.
map
(
this
,
UserDto
.
class
);
var
dto
=
mapper
.
map
(
this
,
UserDto
.
class
);
if
(
admin
)
{
dto
.
getRoles
().
add
(
UserRoleType
.
ADMIN
);
}
if
(
regular
)
{
dto
.
getRoles
().
add
(
UserRoleType
.
USER
);
}
dto
.
setPasswordHash
(
encoder
.
encode
(
password
));
return
dto
;
}
}
winery/webapp/src/main/java/cz/muni/fi/pa165/winery/webapp/models/user/UserUpdateViewModel.java
0 → 100644
View file @
1c421be9
package
cz.muni.fi.pa165.winery.webapp.models.user
;
import
cz.muni.fi.pa165.winery.dto.user.UserDto
;
import
cz.muni.fi.pa165.winery.enums.UserRoleType
;
import
cz.muni.fi.pa165.winery.webapp.models.ViewModelBase
;
import
lombok.Getter
;
import
lombok.NoArgsConstructor
;
import
lombok.Setter
;
import
org.modelmapper.ModelMapper
;
import
org.modelmapper.convention.MatchingStrategies
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
javax.validation.constraints.Email
;
import
javax.validation.constraints.NotNull
;
/**
* @author Jakub Balga
*/
@Getter
@Setter
@NoArgsConstructor
public
class
UserUpdateViewModel
extends
ViewModelBase
{
public
UserUpdateViewModel
(
UserDto
dto
)
{
var
mapper
=
new
ModelMapper
();
mapper
.
getConfiguration
().
setAmbiguityIgnored
(
true
);
mapper
.
getConfiguration
().
setMatchingStrategy
(
MatchingStrategies
.
STRICT
);
mapper
.
map
(
dto
,
this
);
if
(
dto
.
hasRole
(
UserRoleType
.
ADMIN
))
{
admin
=
true
;
}
if
(
dto
.
hasRole
(
UserRoleType
.
USER
))
{
regular
=
true
;
}
}
private
long
id
;
private
boolean
success
;
private
boolean
regular
;
private
boolean
admin
;
/**
* User's first name (forename).
*/
@NotNull
(
message
=
"Cannot be empty"
)
private
String
firstName
;
/**
* User's last name (surname)
*/
@NotNull
(
message
=
"Cannot be empty"
)
private
String
lastName
;
/**
* User's email address
*/
@NotNull
(
message
=
"Cannot be empty"
)
@Email
private
String
email
;
/**
* The user submitted password
*
* Not stored directly
*/
private
String
password
;
public
UserDto
toDto
(
PasswordEncoder
encoder
)
{
var
mapper
=
new
ModelMapper
();
var
dto
=
mapper
.
map
(
this
,
UserDto
.
class
);
if
(
admin
)
{
dto
.
getRoles
().
add
(
UserRoleType
.
ADMIN
);
}
if
(
regular
)
{
dto
.
getRoles
().
add
(
UserRoleType
.
USER
);
}
if
(
password
!=
null
&&
!
password
.
isEmpty
())
{
dto
.
setPasswordHash
(
encoder
.
encode
(
password
));
}
return
dto
;
}
}
winery/webapp/src/main/resources/templates/user/add.html
View file @
1c421be9
...
...
@@ -35,9 +35,17 @@
<p
class=
"text-danger"
th:each=
"error : ${#fields.errors('email')}"
th:text=
"${error}"
></p>
</div>
<div
class=
"form-group"
>
<label
th:for=
"*{passwordHash}"
th:value=
"*{passwordHash}"
>
Password hash
</label>
<input
id=
"passwordHash"
class=
"form-control"
type=
"text"
th:field=
"*{passwordHash}"
>
<p
class=
"text-danger"
th:each=
"error : ${#fields.errors('passwordHash')}"
th:text=
"${error}"
></p>
<label
th:for=
"*{password}"
th:value=
"*{password}"
>
Password
</label>
<input
id=
"password"
class=
"form-control"
type=
"password"
placeholder=
"enter new password"
th:field=
"*{password}"
>
<p
class=
"text-danger"
th:each=
"error : ${#fields.errors('password')}"
th:text=
"${error}"
></p>
</div>
<div
class=
"form-group"
>
<label
th:for=
"*{regular}"
th:value=
"*{regular}"
>
Regular
</label>
<input
id=
"regular"
type=
"checkbox"
th:field=
"*{regular}"
>
</div>
<div
class=
"form-group"
>
<label
th:for=
"*{admin}"
th:value=
"*{admin}"
>
Admin
</label>
<input
id=
"admin"
type=
"checkbox"
th:field=
"*{admin}"
>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
>
Submit
</button>
</form>
...
...
winery/webapp/src/main/resources/templates/user/edit.html
View file @
1c421be9
...
...
@@ -20,8 +20,6 @@
<form
th:action=
"@{/user/edit}"
th:object=
"${model}"
method=
"post"
>
<input
type=
"hidden"
th:field=
"*{id}"
>
<input
type=
"hidden"
th:field=
"*{roles}"
>
<div
class=
"form-group"
>
<label
th:for=
"*{firstName}"
th:value=
"*{firstName}"
>
First name
</label>
<input
id=
"firstName"
class=
"form-control"
type=
"text"
th:field=
"*{firstName}"
>
...
...
@@ -38,9 +36,17 @@
<p
class=
"text-danger"
th:each=
"error : ${#fields.errors('email')}"
th:text=
"${error}"
></p>
</div>
<div
class=
"form-group"
>
<label
th:for=
"*{passwordHash}"
th:value=
"*{passwordHash}"
>
Password hash
</label>
<input
id=
"passwordHash"
class=
"form-control"
type=
"text"
th:field=
"*{passwordHash}"
>
<p
class=
"text-danger"
th:each=
"error : ${#fields.errors('passwordHash')}"
th:text=
"${error}"
></p>
<label
th:for=
"*{password}"
th:value=
"*{password}"
>
Password
</label>
<input
id=
"password"
class=
"form-control"
type=
"password"
placeholder=
"enter new password"
th:field=
"*{password}"
>
<p
class=
"text-danger"
th:each=
"error : ${#fields.errors('password')}"
th:text=
"${error}"
></p>
</div>
<div
class=
"form-group"
>
<label
th:for=
"*{regular}"
th:value=
"*{regular}"
>
Regular
</label>
<input
id=
"regular"
type=
"checkbox"
th:field=
"*{regular}"
>
</div>
<div
class=
"form-group"
>
<label
th:for=
"*{admin}"
th:value=
"*{admin}"
>
Admin
</label>
<input
id=
"admin"
type=
"checkbox"
th:field=
"*{admin}"
>
</div>
<button
type=
"submit"
class=
"btn btn-primary"
>
Submit
</button>
</form>
...
...
winery/webapp/src/main/resources/templates/user/index.html
View file @
1c421be9
...
...
@@ -19,6 +19,7 @@
<th
scope=
"col"
>
First name
</th>
<th
scope=
"col"
>
Last name
</th>
<th
scope=
"col"
>
Email
</th>
<th
scope=
"col"
>
Roles
</th>
<th
scope=
"col"
>
Actions
</th>
</tr>
</thead>
...
...
@@ -27,6 +28,7 @@
<td
th:text=
"${user.firstName}"
>
First name
</td>
<td
th:text=
"${user.lastName}"
>
Last name
</td>
<td
th:text=
"${user.email}"
>
Email
</td>
<td
th:text=
"${user.roles}"
>
Roles
</td>
<td>
<a
th:href=
"@{/user/edit(id=${user.id})}"
>
Edit
</a>
|
<a
href=
"#"
th:attr=
"data-id=${user.id}"
onclick=
"deleteAndReload(this)"
>
Delete
</a></td>
</tr>
</tbody>
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment