Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#ep 16 project landing page #53

Merged
merged 12 commits into from
Nov 5, 2020
Merged
14 changes: 14 additions & 0 deletions src/main/java/io/github/bbortt/event/planner/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.github.bbortt.event.planner.config.Constants;
import io.swagger.models.auth.In;
import java.io.Serializable;
import java.time.ZonedDateTime;
import java.util.HashSet;
Expand All @@ -16,6 +17,7 @@
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
Expand Down Expand Up @@ -109,6 +111,10 @@ public class User extends AbstractAuditingEntity implements Serializable {
@JsonIgnoreProperties(value = "users", allowSetters = true)
private Set<Authority> authorities = new HashSet<>();

@OneToMany(mappedBy = "user")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<Invitation> invitations = new HashSet<>();

public Long getId() {
return id;
}
Expand Down Expand Up @@ -214,6 +220,14 @@ public void setAuthorities(Set<Authority> authorities) {
this.authorities = authorities;
}

public Set<Invitation> getInvitations() {
return this.invitations;
}

public void setInvitations(Set<Invitation> invitations) {
this.invitations = invitations;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.github.bbortt.event.planner.domain.User;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.cache.annotation.Cacheable;
Expand Down Expand Up @@ -31,11 +30,11 @@ public interface UserRepository extends JpaRepository<User, Long> {

Optional<User> findOneByLogin(String login);

@EntityGraph(attributePaths = "authorities")
@EntityGraph(attributePaths = { "authorities", "invitations" })
bbortt marked this conversation as resolved.
Show resolved Hide resolved
@Cacheable(cacheNames = USERS_BY_LOGIN_CACHE)
Optional<User> findOneWithAuthoritiesByLogin(String login);

@EntityGraph(attributePaths = "authorities")
@EntityGraph(attributePaths = { "authorities", "invitations" })
@Cacheable(cacheNames = USERS_BY_EMAIL_CACHE)
Optional<User> findOneWithAuthoritiesByEmailIgnoreCase(String email);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.github.bbortt.event.planner.domain.User;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.constraints.Email;
Expand Down Expand Up @@ -52,6 +53,8 @@ public class UserDTO {

private Set<String> authorities;

private Map<Long, String> rolePerProject;

public UserDTO() {
// Empty constructor needed for Jackson.
}
Expand All @@ -70,6 +73,11 @@ public UserDTO(User user) {
this.lastModifiedBy = user.getLastModifiedBy();
this.lastModifiedDate = ZonedDateTime.ofInstant(user.getLastModifiedDate(), ZoneId.systemDefault());
this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet());
this.rolePerProject =
user
.getInvitations()
.stream()
.collect(Collectors.toMap(invitation -> invitation.getProject().getId(), invitation -> invitation.getRole().getName()));
}

public Long getId() {
Expand Down Expand Up @@ -176,6 +184,14 @@ public void setAuthorities(Set<String> authorities) {
this.authorities = authorities;
}

public Map<Long, String> getRolePerProject() {
return rolePerProject;
}

public void setRolePerProject(Map<Long, String> rolePerProject) {
this.rolePerProject = rolePerProject;
}

// prettier-ignore
@Override
public String toString() {
Expand All @@ -192,6 +208,7 @@ public String toString() {
", lastModifiedBy='" + lastModifiedBy + '\'' +
", lastModifiedDate=" + lastModifiedDate +
", authorities=" + authorities +
", rolePerProject="+ rolePerProject +
"}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
@RestController
@RequestMapping("/api")
public class AccountResource {

private final Logger log = LoggerFactory.getLogger(AccountResource.class);
private final UserRepository userRepository;
private final UserService userService;
Expand Down
4 changes: 2 additions & 2 deletions src/main/webapp/app/account/password/password.route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Route } from '@angular/router';

import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
import { UserRouteAuthorityAccessService } from 'app/core/auth/user-route-authority-access-service';
import { PasswordComponent } from './password.component';
import { AUTHORITY_USER } from 'app/shared/constants/authority.constants';

Expand All @@ -11,5 +11,5 @@ export const passwordRoute: Route = {
authorities: [AUTHORITY_USER],
pageTitle: 'global.menu.account.password',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
};
4 changes: 2 additions & 2 deletions src/main/webapp/app/account/settings/settings.route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Route } from '@angular/router';

import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
import { UserRouteAuthorityAccessService } from 'app/core/auth/user-route-authority-access-service';
import { SettingsComponent } from './settings.component';
import { AUTHORITY_USER } from 'app/shared/constants/authority.constants';

Expand All @@ -11,5 +11,5 @@ export const settingsRoute: Route = {
authorities: [AUTHORITY_USER],
pageTitle: 'global.menu.account.settings',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
};
4 changes: 2 additions & 2 deletions src/main/webapp/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { navbarRoute } from './layouts/navbar/navbar.route';
import { DEBUG_INFO_ENABLED } from 'app/app.constants';
import { AUTHORITY_ADMIN } from 'app/shared/constants/authority.constants';

import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
import { UserRouteAuthorityAccessService } from 'app/core/auth/user-route-authority-access-service';

const LAYOUT_ROUTES = [navbarRoute, ...errorRoute];

Expand All @@ -18,7 +18,7 @@ const LAYOUT_ROUTES = [navbarRoute, ...errorRoute];
data: {
authorities: [AUTHORITY_ADMIN],
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
loadChildren: () => import('./admin/admin-routing.module').then(m => m.AdminRoutingModule),
},
{
Expand Down
16 changes: 16 additions & 0 deletions src/main/webapp/app/core/auth/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { StateStorageService } from 'app/core/auth/state-storage.service';

import { SERVER_API_URL } from 'app/app.constants';
import { Account } from 'app/core/user/account.model';
import { AUTHORITY_ADMIN } from 'app/shared/constants/authority.constants';

@Injectable({ providedIn: 'root' })
export class AccountService {
Expand Down Expand Up @@ -43,6 +44,21 @@ export class AccountService {
return this.userIdentity.authorities.some((authority: string) => authorities.includes(authority));
}

hasAnyRole(projectId: number, roles: string[] | string): boolean {
if (!this.userIdentity || !this.userIdentity.rolePerProject) {
return false;
}
if (this.hasAnyAuthority(AUTHORITY_ADMIN)) {
return true;
}
if (!Array.isArray(roles)) {
roles = [roles];
}

const projectRole = this.userIdentity.rolePerProject[projectId];
return !!projectRole && roles.some((role: string) => projectRole === role);
}

identity(force?: boolean): Observable<Account | null> {
if (!this.accountCache$ || force || !this.isAuthenticated()) {
this.accountCache$ = this.fetch().pipe(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { LoginModalService } from 'app/core/login/login-modal.service';
import { StateStorageService } from './state-storage.service';

@Injectable({ providedIn: 'root' })
export class UserRouteAccessService implements CanActivate {
export class UserRouteAuthorityAccessService implements CanActivate {
constructor(
private router: Router,
private loginModalService: LoginModalService,
Expand All @@ -32,8 +32,7 @@ export class UserRouteAccessService implements CanActivate {
}

if (account) {
const hasAnyAuthority = this.accountService.hasAnyAuthority(authorities);
if (hasAnyAuthority) {
if (this.accountService.hasAnyAuthority(authorities)) {
return true;
}
if (isDevMode()) {
Expand Down
46 changes: 46 additions & 0 deletions src/main/webapp/app/core/auth/user-route-role-access-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Injectable, isDevMode } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

import { AccountService } from 'app/core/auth/account.service';
import { StateStorageService } from './state-storage.service';

@Injectable({ providedIn: 'root' })
export class UserRouteRoleAccessService implements CanActivate {
bbortt marked this conversation as resolved.
Show resolved Hide resolved
constructor(private router: Router, private accountService: AccountService, private stateStorageService: StateStorageService) {}

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
const roles = route.data['roles'];
const projectId = route.params['projectId'];
// We need to call the checkRoles / and so the accountService.identity() function, to ensure,
// that the client has a principal too, if they already logged in by the server.
// This could happen on a page refresh.
return this.checkRoles(projectId, roles, state.url);
}

checkRoles(projectId: number, roles: string[], url: string): Observable<boolean> {
return this.accountService.identity().pipe(
map(account => {
if (!roles || roles.length === 0) {
Marcarrian marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

if (account) {
if (this.accountService.hasAnyRole(projectId, roles)) {
return true;
}
if (isDevMode()) {
console.error('User has not any of required roles: ', roles);
}
this.router.navigate(['accessdenied']);
return false;
}

this.stateStorageService.storeUrl(url);
this.router.navigate(['accessdenied']);
return false;
})
);
}
}
3 changes: 2 additions & 1 deletion src/main/webapp/app/core/user/account.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export class Account {
public langKey: string,
public lastName: string,
public login: string,
public imageUrl: string
public imageUrl: string,
public rolePerProject: Map<number, string>
) {}
}
10 changes: 5 additions & 5 deletions src/main/webapp/app/entities/event/event.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ActivatedRouteSnapshot, Resolve, Router, Routes } from '@angular/router
import { EMPTY, Observable, of } from 'rxjs';
import { flatMap } from 'rxjs/operators';

import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
import { UserRouteAuthorityAccessService } from 'app/core/auth/user-route-authority-access-service';
import { Event, IEvent } from 'app/shared/model/event.model';
import { EventService } from './event.service';
import { EventComponent } from './event.component';
Expand Down Expand Up @@ -41,7 +41,7 @@ export const eventRoute: Routes = [
defaultSort: 'id,asc',
pageTitle: 'eventPlannerApp.event.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
{
path: ':id/view',
Expand All @@ -52,7 +52,7 @@ export const eventRoute: Routes = [
data: {
pageTitle: 'eventPlannerApp.event.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
{
path: 'new',
Expand All @@ -63,7 +63,7 @@ export const eventRoute: Routes = [
data: {
pageTitle: 'eventPlannerApp.event.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
{
path: ':id/edit',
Expand All @@ -74,6 +74,6 @@ export const eventRoute: Routes = [
data: {
pageTitle: 'eventPlannerApp.event.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
];
10 changes: 5 additions & 5 deletions src/main/webapp/app/entities/invitation/invitation.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ActivatedRouteSnapshot, Resolve, Router, Routes } from '@angular/router
import { EMPTY, Observable, of } from 'rxjs';
import { flatMap } from 'rxjs/operators';

import { UserRouteAccessService } from 'app/core/auth/user-route-access-service';
import { UserRouteAuthorityAccessService } from 'app/core/auth/user-route-authority-access-service';
import { IInvitation, Invitation } from 'app/shared/model/invitation.model';
import { InvitationService } from './invitation.service';
import { InvitationComponent } from './invitation.component';
Expand Down Expand Up @@ -41,7 +41,7 @@ export const invitationRoute: Routes = [
defaultSort: 'id,asc',
pageTitle: 'eventPlannerApp.invitation.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
{
path: ':id/view',
Expand All @@ -52,7 +52,7 @@ export const invitationRoute: Routes = [
data: {
pageTitle: 'eventPlannerApp.invitation.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
{
path: 'new',
Expand All @@ -63,7 +63,7 @@ export const invitationRoute: Routes = [
data: {
pageTitle: 'eventPlannerApp.invitation.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
{
path: ':id/edit',
Expand All @@ -74,6 +74,6 @@ export const invitationRoute: Routes = [
data: {
pageTitle: 'eventPlannerApp.invitation.home.title',
},
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAuthorityAccessService],
},
];