user access dates

This commit is contained in:
Michele Artini 2020-11-12 15:47:41 +01:00
parent e240af83fb
commit 1b00ac2b4f
6 changed files with 62 additions and 13 deletions

View File

@ -28,15 +28,15 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import eu.dnetlib.organizations.controller.UserInfo; import eu.dnetlib.organizations.controller.UserInfo;
import eu.dnetlib.organizations.controller.UserRole; import eu.dnetlib.organizations.controller.UserRole;
import eu.dnetlib.organizations.model.User; import eu.dnetlib.organizations.model.User;
import eu.dnetlib.organizations.repository.UserRepository;
import eu.dnetlib.organizations.utils.AuthenticationUtils; import eu.dnetlib.organizations.utils.AuthenticationUtils;
import eu.dnetlib.organizations.utils.DatabaseUtils;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
private UserRepository userRepository; private DatabaseUtils databaseUtils;
@Autowired @Autowired
private ClientRegistrationRepository clientRegistrationRepository; private ClientRegistrationRepository clientRegistrationRepository;
@ -114,7 +114,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
return (userRequest) -> { return (userRequest) -> {
final OidcUser oidcUser = delegate.loadUser(userRequest); final OidcUser oidcUser = delegate.loadUser(userRequest);
final String role = "ROLE_" + OPENORGS_ROLE_PREFIX + userRepository.findById(oidcUser.getEmail()) final String role = "ROLE_" + OPENORGS_ROLE_PREFIX + databaseUtils.findUser(oidcUser.getEmail())
.map(User::getRole) .map(User::getRole)
.filter(StringUtils::isNotBlank) .filter(StringUtils::isNotBlank)
.orElse(UserRole.NOT_AUTHORIZED.toString()); .orElse(UserRole.NOT_AUTHORIZED.toString());

View File

@ -1,6 +1,7 @@
package eu.dnetlib.organizations.model.view; package eu.dnetlib.organizations.model.view;
import java.io.Serializable; import java.io.Serializable;
import java.time.OffsetDateTime;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
@ -16,7 +17,7 @@ import com.vladmihalcea.hibernate.type.array.StringArrayType;
@Entity @Entity
@Table(name = "users_view") @Table(name = "users_view")
@TypeDefs({ @TypeDefs({
@TypeDef(name = "string-array", typeClass = StringArrayType.class) @TypeDef(name = "string-array", typeClass = StringArrayType.class)
}) })
public class UserView implements Serializable { public class UserView implements Serializable {
@ -35,6 +36,12 @@ public class UserView implements Serializable {
@Column(name = "role") @Column(name = "role")
private String role; private String role;
@Column(name = "first_access")
private OffsetDateTime firstAccess;
@Column(name = "last_access")
private OffsetDateTime lastAccess;
@Type(type = "string-array") @Type(type = "string-array")
@Column(name = "countries", columnDefinition = "text[]") @Column(name = "countries", columnDefinition = "text[]")
private String[] countries; private String[] countries;
@ -70,4 +77,20 @@ public class UserView implements Serializable {
public void setCountries(final String[] countries) { public void setCountries(final String[] countries) {
this.countries = countries; this.countries = countries;
} }
public OffsetDateTime getFirstAccess() {
return firstAccess;
}
public void setFirstAccess(final OffsetDateTime firstAccess) {
this.firstAccess = firstAccess;
}
public OffsetDateTime getLastAccess() {
return lastAccess;
}
public void setLastAccess(final OffsetDateTime lastAccess) {
this.lastAccess = lastAccess;
}
} }

View File

@ -1,9 +1,17 @@
package eu.dnetlib.organizations.repository; package eu.dnetlib.organizations.repository;
import java.time.OffsetDateTime;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import eu.dnetlib.organizations.model.User; import eu.dnetlib.organizations.model.User;
public interface UserRepository extends JpaRepository<User, String> { public interface UserRepository extends JpaRepository<User, String> {
@Modifying
@Query("update User set last_access = ?2 where email = ?1")
void updateLastAccess(final String email, OffsetDateTime now);
} }

View File

@ -401,6 +401,15 @@ public class DatabaseUtils {
} }
} }
@Transactional
public Optional<User> findUser(final String email) {
final Optional<User> user = userRepository.findById(email);
if (user.isPresent()) {
userRepository.updateLastAccess(email, OffsetDateTime.now());
}
return user;
}
private String findFirstString(final List<OrganizationView> views, final Function<OrganizationView, String> mapper) { private String findFirstString(final List<OrganizationView> views, final Function<OrganizationView, String> mapper) {
return views.stream().map(mapper).filter(StringUtils::isNotBlank).findFirst().orElse(null); return views.stream().map(mapper).filter(StringUtils::isNotBlank).findFirst().orElse(null);
} }

View File

@ -310,9 +310,11 @@ CREATE TABLE user_roles(role text PRIMARY KEY);
INSERT INTO user_roles VALUES ('ADMIN'), ('NATIONAL_ADMIN'), ('USER'), ('PENDING'), ('NOT_AUTHORIZED'); INSERT INTO user_roles VALUES ('ADMIN'), ('NATIONAL_ADMIN'), ('USER'), ('PENDING'), ('NOT_AUTHORIZED');
CREATE TABLE users ( CREATE TABLE users (
email text PRIMARY KEY, email text PRIMARY KEY,
valid boolean DEFAULT true, valid boolean DEFAULT true,
role text NOT NULL default 'USER' REFERENCES user_roles(role) role text NOT NULL default 'USER' REFERENCES user_roles(role),
first_access timestamp with time zone DEFAULT now(),
last_access timestamp with time zone DEFAULT now()
); );
CREATE TABLE user_countries ( CREATE TABLE user_countries (
@ -507,11 +509,13 @@ CREATE VIEW users_view AS SELECT
u.email, u.email,
u.valid, u.valid,
u.role, u.role,
u.first_access,
u.last_access,
array_remove(array_agg(uc.country), NULL) AS countries array_remove(array_agg(uc.country), NULL) AS countries
FROM FROM
users u users u
LEFT OUTER JOIN user_countries uc ON (u.email = uc.email) LEFT OUTER JOIN user_countries uc ON (u.email = uc.email)
GROUP BY u.email, u.valid, u.role GROUP BY u.email, u.valid, u.role, u.first_access, u.last_access
ORDER BY u.email; ORDER BY u.email;
CREATE VIEW suggestions_info_by_country_view AS SELECT c.val AS country, CREATE VIEW suggestions_info_by_country_view AS SELECT c.val AS country,

View File

@ -2,23 +2,24 @@
<br /> <br />
<div class="row"> <div class="row">
<div class="col-sm-12 col-md-10 col-lg-8"> <div class="col-sm-12 col-lg-10">
<input type="text" class="form-control form-control-sm mb-3" ng-model="userFilter.email" placeholder="Filter..."> <input type="text" class="form-control form-control-sm mb-3" ng-model="userFilter.email" placeholder="Filter...">
<table class="table table-sm table-hover"> <table class="table table-sm table-hover">
<thead class="thead-light"> <thead class="thead-light">
<tr class="d-flex"> <tr class="d-flex">
<th class="col-4">User</th> <th class="col-3">User</th>
<th class="col-1 text-center">Enabled</th> <th class="col-1 text-center">Enabled</th>
<th class="col-1 text-center">Super Admin</th> <th class="col-1 text-center">Super Admin</th>
<th class="col-1 text-center">National Admin</th> <th class="col-1 text-center">National Admin</th>
<th class="col-3">Countries</th> <th class="col-2 text-center">First/Last access</th>
<th class="col-2">Countries</th>
<th class="col-2"></th> <th class="col-2"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="u in users | filter:userFilter" class="d-flex"> <tr ng-repeat="u in users | filter:userFilter" class="d-flex">
<th class="col-4" ng-class="{'text-secondary': !u.valid}">{{u.email}}</th> <th class="col-3" ng-class="{'text-secondary': !u.valid}">{{u.email}}</th>
<td class="col-1 text-center text-success"> <td class="col-1 text-center text-success">
<i class="fa fa-check-circle" ng-if="u.valid"></i> <i class="fa fa-check-circle" ng-if="u.valid"></i>
<span class="text-warning" ng-if="u.role == 'PENDING'">not configured</span> <span class="text-warning" ng-if="u.role == 'PENDING'">not configured</span>
@ -30,7 +31,11 @@
<td class="col-1 text-center"> <td class="col-1 text-center">
<i class="fa fa-check-circle" ng-if="u.role == 'NATIONAL_ADMIN'"></i> <i class="fa fa-check-circle" ng-if="u.role == 'NATIONAL_ADMIN'"></i>
</td> </td>
<td class="col-3"> <td class="col-2 text-center">
<span title="{{u.firstAccess}}">{{u.firstAccess | date}}</span><br />
<span title="{{u.lastAccess}}">{{u.lastAccess | date}}</span>
</td>
<td class="col-2">
<span ng-if="(u.role != 'ADMIN')"> <span ng-if="(u.role != 'ADMIN')">
<img ng-src="resources/images/flags/{{c}}.gif" title="{{c}}" class="mr-1" ng-repeat="c in u.countries" /> <img ng-src="resources/images/flags/{{c}}.gif" title="{{c}}" class="mr-1" ng-repeat="c in u.countries" />
<span class="text-warning" ng-if="u.countries.length == 0"><i class="fa fa-exclamation-triangle"></i> no countries</span> <span class="text-warning" ng-if="u.countries.length == 0"><i class="fa fa-exclamation-triangle"></i> no countries</span>