urls with authorizations

This commit is contained in:
Michele Artini 2020-11-04 14:18:41 +01:00
parent 7d25ad2928
commit 6a5488811e
11 changed files with 113 additions and 54 deletions

View File

@ -15,6 +15,7 @@ import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import eu.dnetlib.organizations.controller.UserInfo;
import eu.dnetlib.organizations.utils.AuthenticationUtils;
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
@ -25,13 +26,15 @@ public class MyAccessDeniedHandler implements AccessDeniedHandler {
public void handle(final HttpServletRequest req, final HttpServletResponse res, final AccessDeniedException e)
throws IOException, ServletException {
final Authentication auth = SecurityContextHolder.getContext().getAuthentication();
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
logger.warn(String.format("User '%s' (%s) attempted to access the protected URL: %s", auth.getName(), req.getRemoteAddr(), req.getRequestURI()));
if (authentication != null) {
logger.warn(String
.format("User '%s' (%s) attempted to access the protected URL: %s", AuthenticationUtils.extractEmail(authentication), req.getRemoteAddr(), req
.getRequestURI()));
}
if (UserInfo.isNotAuthorized(auth)) {
if (UserInfo.isNotAuthorized(authentication)) {
res.sendRedirect(req.getContextPath() + "/authorizationRequest");
} else {
res.sendRedirect(req.getContextPath() + "/alreadyRegistered");

View File

@ -1,9 +1,9 @@
package eu.dnetlib.organizations;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@ -17,6 +17,7 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.access.AccessDeniedHandler;
import eu.dnetlib.organizations.controller.UserRole;
import eu.dnetlib.organizations.model.User;
@ -29,14 +30,44 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserRepository userRepository;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Value("${openaire.api.valid.subnet}")
private String openaireApiValidSubnet;
private static String OPENORGS_ROLE_PREFIX = "OPENORGS_";
private static final String[] VALID_ROLES = {
OPENORGS_ROLE_PREFIX + UserRole.ADMIN,
OPENORGS_ROLE_PREFIX + UserRole.NATIONAL_ADMIN,
OPENORGS_ROLE_PREFIX + UserRole.USER
};
private static final String NOT_AUTORIZED_ROLE = OPENORGS_ROLE_PREFIX + UserRole.NOT_AUTHORIZED;
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())));
http.csrf()
.disable()
.authorizeRequests()
.antMatchers("/", "/api/**")
.hasAnyRole(VALID_ROLES)
.antMatchers("/registration_api/**")
.hasRole(NOT_AUTORIZED_ROLE)
.antMatchers("/resources/**", "/webjars/**")
.permitAll()
.antMatchers("/oa_api/**")
.hasIpAddress(openaireApiValidSubnet)
.anyRequest()
.authenticated()
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.and()
.oauth2Login(oauth2 -> oauth2.userInfoEndpoint(userInfo -> userInfo.oidcUserService(this.oidcUserService())));
}
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
@ -45,11 +76,13 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
return (userRequest) -> {
final OidcUser oidcUser = delegate.loadUser(userRequest);
final Optional<User> user = userRepository.findById(oidcUser.getEmail());
final String role = user.isPresent() ? user.get().getRole() : UserRole.PENDING.toString();
final String role = "ROLE_" + OPENORGS_ROLE_PREFIX + userRepository.findById(oidcUser.getEmail())
.map(User::getRole)
.filter(StringUtils::isNotBlank)
.orElse(UserRole.NOT_AUTHORIZED.toString());
final Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
mappedAuthorities.add(new SimpleGrantedAuthority("ROLE_OPENORGS_" + role));
mappedAuthorities.add(new SimpleGrantedAuthority(role));
return new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
};

View File

@ -41,6 +41,7 @@ import eu.dnetlib.organizations.repository.readonly.OrganizationInfoViewReposito
import eu.dnetlib.organizations.repository.readonly.OrganizationSimpleViewRepository;
import eu.dnetlib.organizations.repository.readonly.OrganizationViewRepository;
import eu.dnetlib.organizations.repository.readonly.SuggestionInfoViewByCountryRepository;
import eu.dnetlib.organizations.utils.AuthenticationUtils;
import eu.dnetlib.organizations.utils.DatabaseUtils;
import eu.dnetlib.organizations.utils.OrganizationStatus;
@ -76,12 +77,14 @@ public class OrganizationController {
throw new RuntimeException("Missing field: country");
} else if (StringUtils.isBlank(org.getType())) {
throw new RuntimeException("Missing field: type");
} else if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
final String orgId = databaseUtils.insertOrUpdateOrganization(org, authentication.getName(), UserInfo.isSimpleUser(authentication));
return Arrays.asList(orgId);
} else {
throw new RuntimeException("User not authorized");
}
} else if (UserInfo.isSuperAdmin(authentication)
|| userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), AuthenticationUtils.extractEmail(authentication))) {
final String orgId =
databaseUtils.insertOrUpdateOrganization(org, AuthenticationUtils.extractEmail(authentication), UserInfo.isSimpleUser(authentication));
return Arrays.asList(orgId);
} else {
throw new RuntimeException("User not authorized");
}
}
@GetMapping("/info")
@ -96,7 +99,7 @@ public class OrganizationController {
if (UserInfo.isSuperAdmin(authentication)) {
suggestionInfoViewByCountryRepository.findAll().forEach(info::add);
} else if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
userCountryRepository.getCountriesForUser(authentication.getName())
userCountryRepository.getCountriesForUser(AuthenticationUtils.extractEmail(authentication))
.stream()
.map(suggestionInfoViewByCountryRepository::findById)
.filter(Optional::isPresent)
@ -110,7 +113,8 @@ public class OrganizationController {
public OrganizationView findById(@RequestParam final String id, final Authentication authentication) {
final OrganizationView org = organizationViewRepository.findById(id).get();
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), authentication.getName())) {
if (UserInfo.isSuperAdmin(authentication)
|| userCountryRepository.verifyAuthorizationForCountry(org.getCountry(), AuthenticationUtils.extractEmail(authentication))) {
return org;
} else {
throw new RuntimeException("User not authorized");
@ -119,7 +123,7 @@ public class OrganizationController {
@GetMapping("/conflicts")
public List<OrganizationConflict> conflicts(@RequestParam final String id, final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName())) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, AuthenticationUtils.extractEmail(authentication))) {
return databaseUtils.listConflictsForId(id);
} else {
throw new RuntimeException("User not authorized");
@ -128,7 +132,7 @@ public class OrganizationController {
@GetMapping("/duplicates")
public List<OpenaireDuplicateView> duplicates(@RequestParam final String id, final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, authentication.getName())) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(id, AuthenticationUtils.extractEmail(authentication))) {
return openaireDuplicateViewRepository.findByLocalId(id);
} else {
throw new RuntimeException("User not authorized");
@ -141,7 +145,7 @@ public class OrganizationController {
if (UserInfo.isSuperAdmin(authentication)) {
return groupConflicts(conflictGroupViewRepository.findByCountry1OrCountry2(country, country).stream());
} else if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
final Stream<ConflictGroupView> list = userCountryRepository.getCountriesForUser(authentication.getName())
final Stream<ConflictGroupView> list = userCountryRepository.getCountriesForUser(AuthenticationUtils.extractEmail(authentication))
.stream()
.filter(country::equalsIgnoreCase)
.map(c -> conflictGroupViewRepository.findByCountry1OrCountry2(c, c).stream())
@ -160,7 +164,7 @@ public class OrganizationController {
if (UserInfo.isSuperAdmin(authentication)) {
return duplicateGroupViewRepository.findByCountry(country);
} else if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
return userCountryRepository.getCountriesForUser(authentication.getName())
return userCountryRepository.getCountriesForUser(AuthenticationUtils.extractEmail(authentication))
.stream()
.filter(country::equalsIgnoreCase)
.map(duplicateGroupViewRepository::findByCountry)
@ -193,10 +197,10 @@ public class OrganizationController {
|| simrels.stream()
.map(OpenaireDuplicate::getLocalId)
.distinct()
.allMatch(id -> userCountryRepository.verifyAuthorizationForId(id, authentication.getName()));
.allMatch(id -> userCountryRepository.verifyAuthorizationForId(id, AuthenticationUtils.extractEmail(authentication)));
if (b) {
databaseUtils.saveDuplicates(simrels, authentication.getName());
databaseUtils.saveDuplicates(simrels, AuthenticationUtils.extractEmail(authentication));
return openaireDuplicateViewRepository.findByLocalId(simrels.get(0).getLocalId());
} else {
throw new RuntimeException("User not authorized");
@ -222,7 +226,7 @@ public class OrganizationController {
return UserInfo.isSuperAdmin(authentication)
? organizationSimpleViewRepository.search(q, statuses, PageRequest.of(page, size))
: organizationSimpleViewRepository.searchForUser(q, authentication.getName(), statuses, PageRequest.of(page, size));
: organizationSimpleViewRepository.searchForUser(q, AuthenticationUtils.extractEmail(authentication), statuses, PageRequest.of(page, size));
}
@ -232,7 +236,8 @@ public class OrganizationController {
@PathVariable final int page,
@PathVariable final int size,
final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(code, authentication.getName())) {
if (UserInfo.isSuperAdmin(authentication)
|| userCountryRepository.verifyAuthorizationForCountry(code, AuthenticationUtils.extractEmail(authentication))) {
if (status.equalsIgnoreCase("all")) {
return organizationSimpleViewRepository.findByCountryOrderByName(code, PageRequest.of(page, size));
} else {
@ -247,7 +252,8 @@ public class OrganizationController {
public Iterable<OrganizationSimpleView> findOrgsByStatusAndCountry(@PathVariable final String status,
@PathVariable final String code,
final Authentication authentication) {
if (UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForCountry(code, authentication.getName())) {
if (UserInfo.isSuperAdmin(authentication)
|| userCountryRepository.verifyAuthorizationForCountry(code, AuthenticationUtils.extractEmail(authentication))) {
if (status.equalsIgnoreCase("all")) {
return organizationSimpleViewRepository.findByCountryOrderByName(code);
} else {
@ -273,9 +279,10 @@ public class OrganizationController {
}
} else {
if (status.equalsIgnoreCase("all")) {
return organizationSimpleViewRepository.findByTypeForUser(type, authentication.getName(), PageRequest.of(page, size));
return organizationSimpleViewRepository.findByTypeForUser(type, AuthenticationUtils.extractEmail(authentication), PageRequest.of(page, size));
} else {
return organizationSimpleViewRepository.findByTypeAndStatusForUser(type, status, authentication.getName(), PageRequest.of(page, size));
return organizationSimpleViewRepository
.findByTypeAndStatusForUser(type, status, AuthenticationUtils.extractEmail(authentication), PageRequest.of(page, size));
}
}
@ -285,20 +292,21 @@ public class OrganizationController {
public List<BrowseEntry> browseCountries(final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? databaseUtils.browseCountries()
: databaseUtils.browseCountriesForUser(authentication.getName());
: databaseUtils.browseCountriesForUser(AuthenticationUtils.extractEmail(authentication));
}
@GetMapping("/browse/types")
public List<BrowseEntry> browseOrganizationTypes(final Authentication authentication) {
return UserInfo.isSuperAdmin(authentication)
? databaseUtils.browseTypes()
: databaseUtils.browseTypesForUser(authentication.getName());
: databaseUtils.browseTypesForUser(AuthenticationUtils.extractEmail(authentication));
}
@PostMapping("/conflicts/fix/similar")
public List<String> fixConflictSim(final Authentication authentication, @RequestBody final List<String> ids) {
if (ids.size() > 1 && UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(ids.get(0), authentication.getName())) {
final String newOrgId = databaseUtils.fixConflictSimilars(ids, authentication.getName());
if (ids.size() > 1 && UserInfo.isSuperAdmin(authentication)
|| userCountryRepository.verifyAuthorizationForId(ids.get(0), AuthenticationUtils.extractEmail(authentication))) {
final String newOrgId = databaseUtils.fixConflictSimilars(ids, AuthenticationUtils.extractEmail(authentication));
return Arrays.asList(newOrgId);
} else {
return new ArrayList<>();
@ -307,8 +315,9 @@ public class OrganizationController {
@PostMapping("/conflicts/fix/different")
public List<String> fixConflictDiff(final Authentication authentication, @RequestBody final List<String> ids) {
if (ids.size() > 1 && UserInfo.isSuperAdmin(authentication) || userCountryRepository.verifyAuthorizationForId(ids.get(0), authentication.getName())) {
databaseUtils.fixConflictDifferents(ids, authentication.getName());
if (ids.size() > 1 && UserInfo.isSuperAdmin(authentication)
|| userCountryRepository.verifyAuthorizationForId(ids.get(0), AuthenticationUtils.extractEmail(authentication))) {
databaseUtils.fixConflictDifferents(ids, AuthenticationUtils.extractEmail(authentication));
return ids;
} else {
return new ArrayList<>();

View File

@ -20,6 +20,7 @@ import eu.dnetlib.organizations.model.utils.VocabularyTerm;
import eu.dnetlib.organizations.model.view.UserView;
import eu.dnetlib.organizations.repository.UserRepository;
import eu.dnetlib.organizations.repository.readonly.UserViewRepository;
import eu.dnetlib.organizations.utils.AuthenticationUtils;
import eu.dnetlib.organizations.utils.DatabaseUtils;
@RestController
@ -36,7 +37,7 @@ public class UserController {
@PostMapping(value = "/registration_api/newUser")
public Map<String, Integer> newUser(final @RequestBody List<String> countries, final Authentication authentication) {
final String email = authentication.getName();
final String email = AuthenticationUtils.extractEmail(authentication);
final Map<String, Integer> res = new HashMap<>();
@ -57,7 +58,7 @@ public class UserController {
// IMPORTANT: a national admin can manage ONLY the users where ALL the countries are under his control
final List<UserView> res = new ArrayList<>();
final List<String> myCountries = dbUtils.listCountriesForUser(authentication.getName())
final List<String> myCountries = dbUtils.listCountriesForUser(AuthenticationUtils.extractEmail(authentication))
.stream()
.map(VocabularyTerm::getValue)
.collect(Collectors.toList());
@ -75,14 +76,14 @@ public class UserController {
@PostMapping("/api/users")
public Iterable<UserView> save(@RequestBody final UserView userView, final Authentication authentication) {
if (authentication.getName().equals(userView.getEmail())) { throw new RuntimeException("You can't edit your own user"); }
if (AuthenticationUtils.extractEmail(authentication).equals(userView.getEmail())) { throw new RuntimeException("You can't edit your own user"); }
dbUtils.saveUser(userView);
return users(authentication);
}
@DeleteMapping("/api/users")
public Iterable<UserView> delete(final @RequestParam String email, final Authentication authentication) {
if (authentication.getName().equals(email)) { throw new RuntimeException("You can't delete your own user"); }
if (AuthenticationUtils.extractEmail(authentication).equals(email)) { throw new RuntimeException("You can't delete your own user"); }
dbUtils.deleteUser(email);
return users(authentication);
}

View File

@ -5,6 +5,8 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import eu.dnetlib.organizations.utils.AuthenticationUtils;
public class UserInfo {
private String name;
@ -37,18 +39,18 @@ public class UserInfo {
}
public static UserInfo generate(final Authentication authentication) {
return new UserInfo(authentication.getName(), findRole(authentication));
return new UserInfo(AuthenticationUtils.extractEmail(authentication), findRole(authentication));
}
public static UserRole findRole(final Authentication authentication) {
return authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.map(s -> StringUtils.substringAfter(s, "ROLE_OPENORGS_"))
.filter(s -> EnumUtils.isValidEnum(UserRole.class, s))
.map(UserRole::valueOf)
.findFirst()
.orElseGet(() -> UserRole.NOT_AUTHORIZED);
.stream()
.map(GrantedAuthority::getAuthority)
.map(s -> StringUtils.substringAfter(s, "ROLE_OPENORGS_"))
.filter(s -> EnumUtils.isValidEnum(UserRole.class, s))
.map(UserRole::valueOf)
.findFirst()
.orElseGet(() -> UserRole.NOT_AUTHORIZED);
}
public static boolean isSuperAdmin(final Authentication authentication) {

View File

@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import eu.dnetlib.organizations.model.utils.VocabularyTerm;
import eu.dnetlib.organizations.utils.AuthenticationUtils;
import eu.dnetlib.organizations.utils.DatabaseUtils;
import eu.dnetlib.organizations.utils.DatabaseUtils.VocabularyTable;
import eu.dnetlib.organizations.utils.RelationType;
@ -39,7 +40,7 @@ public class VocabulariesController {
.collect(Collectors.toList()));
if (UserInfo.isSimpleUser(authentication) || UserInfo.isNationalAdmin(authentication)) {
vocs.put("countries", databaseUtils.listCountriesForUser(authentication.getName()));
vocs.put("countries", databaseUtils.listCountriesForUser(AuthenticationUtils.extractEmail(authentication)));
} else if (UserInfo.isSuperAdmin(authentication)) {
vocs.put("countries", databaseUtils.listValuesOfVocabularyTable(VocabularyTable.countries));
} else {

View File

@ -0,0 +1,12 @@
package eu.dnetlib.organizations.utils;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
public class AuthenticationUtils {
public static String extractEmail(final Authentication authentication) {
final Object user = authentication.getPrincipal();
return user instanceof DefaultOidcUser ? ((DefaultOidcUser) user).getEmail() : authentication.getName();
}
}

View File

@ -316,8 +316,8 @@ CREATE TABLE users (
);
CREATE TABLE user_countries (
email text REFERENCES users(email),
country text REFERENCES countries(val),
email text REFERENCES users(email) ON UPDATE CASCADE ON DELETE CASCADE,
country text REFERENCES countries(val),
PRIMARY KEY(email, country)
);

View File

@ -29,7 +29,7 @@
<div class="card-body">
<h5 class="card-title">Already registered</h5>
<p class="card-text" th:inline="text">
Hello '[[${#httpServletRequest.remoteUser}]]', you are already registered as <span sec:authentication="principal.authorities"></span>
Hello '<span sec:authentication="principal.email" id="current_user"></span>', you are already registered as <span sec:authentication="principal.authorities"></span>
</p>
</div>
</div>

View File

@ -34,7 +34,7 @@
<div class="card-body">
<h5 class="card-title">Authorization request</h5>
<p class="card-text" th:inline="text">
Hello '[[${#httpServletRequest.remoteUser}]]', you don't have a role yet <br />
Hello '<span sec:authentication="principal.email" id="current_user"></span>', you don't have a role yet <br />
To apply as data curator compile the form below, an administrator will authorize you as soon as possible.
</p>

View File

@ -124,8 +124,6 @@ fieldset > legend { font-size : 1.2rem !important; }
</nav>
<p sec:authentication="principal"></p>
<div class="container-fluid small mt-4" ng-view></div>
<script sec:authorize="hasRole('ROLE_OPENORGS_ADMIN')">