From 07da2278cbd939dcd9e00ddcc90013e2a8285c7b Mon Sep 17 00:00:00 2001 From: George Kalampokis Date: Fri, 3 Apr 2020 18:40:03 +0300 Subject: [PATCH] Add Zenodo Login and the ability to use it's access token for DOI creation --- .../main/java/eu/eudat/controllers/Login.java | 25 ++++-- .../model/models/PrincipalBuilder.java | 15 ++++ .../managers/DataManagementPlanManager.java | 15 ++-- .../Zenodo/ZenodoCustomProvider.java | 8 ++ .../Zenodo/ZenodoCustomProviderImpl.java | 72 +++++++++++++++ .../customproviders/Zenodo/ZenodoUser.java | 49 ++++++++++ .../validators/TokenValidatorFactoryImpl.java | 14 ++- .../zenodo/ZenodoTokenValidator.java | 57 ++++++++++++ .../zenodo/helpers/ZenodoRequest.java | 12 +++ .../zenodo/helpers/ZenodoResponseToken.java | 38 ++++++++ .../AbstractAuthenticationService.java | 13 ++- ...erifiedUserEmailAuthenticationService.java | 15 ++++ .../VerifiedUserAuthenticationService.java | 15 ++++ .../data/loginprovider/LoginProviderUser.java | 18 ++++ .../eudat/models/data/security/Principal.java | 19 ++++ .../config/application-devel.properties | 4 + .../src/app/core/common/enum/auth-provider.ts | 3 +- .../login-providers.model.ts | 6 ++ .../core/model/zenodo/zenodo-token.model.ts | 6 ++ .../ui/auth/login/img/zenodo-white-200.png | Bin 0 -> 2383 bytes .../app/ui/auth/login/login.component.html | 6 ++ .../app/ui/auth/login/login.component.scss | 19 ++++ .../src/app/ui/auth/login/login.component.ts | 9 ++ .../src/app/ui/auth/login/login.module.ts | 4 +- .../src/app/ui/auth/login/login.routing.ts | 4 +- .../zenodo-login/zenodo-login.component.html | 0 .../zenodo-login/zenodo-login.component.scss | 0 .../zenodo-login/zenodo-login.component.ts | 85 ++++++++++++++++++ dmp-frontend/src/assets/config/config.json | 7 +- 29 files changed, 518 insertions(+), 20 deletions(-) create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProvider.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProviderImpl.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoUser.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/ZenodoTokenValidator.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoRequest.java create mode 100644 dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoResponseToken.java create mode 100644 dmp-frontend/src/app/core/model/zenodo/zenodo-token.model.ts create mode 100644 dmp-frontend/src/app/ui/auth/login/img/zenodo-white-200.png create mode 100644 dmp-frontend/src/app/ui/auth/login/zenodo-login/zenodo-login.component.html create mode 100644 dmp-frontend/src/app/ui/auth/login/zenodo-login/zenodo-login.component.scss create mode 100644 dmp-frontend/src/app/ui/auth/login/zenodo-login/zenodo-login.component.ts diff --git a/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java b/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java index 78304aa20..16af6f14f 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java +++ b/dmp-backend/web/src/main/java/eu/eudat/controllers/Login.java @@ -22,6 +22,9 @@ import eu.eudat.logic.security.validators.orcid.ORCIDTokenValidator; import eu.eudat.logic.security.validators.orcid.helpers.ORCIDRequest; import eu.eudat.logic.security.validators.orcid.helpers.ORCIDResponseToken; import eu.eudat.logic.security.validators.twitter.TwitterTokenValidator; +import eu.eudat.logic.security.validators.zenodo.ZenodoTokenValidator; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoRequest; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoResponseToken; import eu.eudat.logic.services.operations.authentication.AuthenticationService; import eu.eudat.models.data.helpers.responses.ResponseItem; import eu.eudat.models.data.login.Credentials; @@ -54,28 +57,34 @@ public class Login { private LinkedInTokenValidator linkedInTokenValidator; private OpenAIRETokenValidator openAIRETokenValidator; private ConfigurableProviderTokenValidator configurableProviderTokenValidator; + private ZenodoTokenValidator zenodoTokenValidator; private ConfigLoader configLoader; // private Logger logger; private UserManager userManager; + @Autowired - public Login(CustomAuthenticationProvider customAuthenticationProvider, AuthenticationService nonVerifiedUserAuthenticationService, - TwitterTokenValidator twitterTokenValidator, LinkedInTokenValidator linkedInTokenValidator, B2AccessTokenValidator b2AccessTokenValidator, - ORCIDTokenValidator orcidTokenValidator, OpenAIRETokenValidator openAIRETokenValidator, ConfigurableProviderTokenValidator configurableProviderTokenValidator, ConfigLoader configLoader, UserManager userManager/*, Logger logger*/) { + public Login(CustomAuthenticationProvider customAuthenticationProvider, + AuthenticationService nonVerifiedUserAuthenticationService, TwitterTokenValidator twitterTokenValidator, + B2AccessTokenValidator b2AccessTokenValidator, ORCIDTokenValidator orcidTokenValidator, + LinkedInTokenValidator linkedInTokenValidator, OpenAIRETokenValidator openAIRETokenValidator, + ConfigurableProviderTokenValidator configurableProviderTokenValidator, ZenodoTokenValidator zenodoTokenValidator, + ConfigLoader configLoader, UserManager userManager) { this.customAuthenticationProvider = customAuthenticationProvider; this.nonVerifiedUserAuthenticationService = nonVerifiedUserAuthenticationService; this.twitterTokenValidator = twitterTokenValidator; - this.linkedInTokenValidator = linkedInTokenValidator; this.b2AccessTokenValidator = b2AccessTokenValidator; this.orcidTokenValidator = orcidTokenValidator; + this.linkedInTokenValidator = linkedInTokenValidator; this.openAIRETokenValidator = openAIRETokenValidator; this.configurableProviderTokenValidator = configurableProviderTokenValidator; + this.zenodoTokenValidator = zenodoTokenValidator; this.configLoader = configLoader; -// this.logger = logger; this.userManager = userManager; } + @Transactional @RequestMapping(method = RequestMethod.POST, value = {"/externallogin"}, consumes = "application/json", produces = "application/json") public @ResponseBody @@ -128,6 +137,12 @@ public class Login { return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(this.configurableProviderTokenValidator.getAccessToken(configurableProviderRequest)).status(ApiMessageCode.NO_MESSAGE)); } + @RequestMapping(method = RequestMethod.POST, value = {"/zenodoRequestToken"}, produces = "application/json", consumes = "application/json") + public @ResponseBody + ResponseEntity> ZenodoRequestToken(@RequestBody ZenodoRequest zenodoRequest) { + return ResponseEntity.status(HttpStatus.OK).body(new ResponseItem().payload(this.zenodoTokenValidator.getAccessToken(zenodoRequest)).status(ApiMessageCode.NO_MESSAGE)); + } + @RequestMapping(method = RequestMethod.POST, value = {"/me"}, consumes = "application/json", produces = "application/json") public @ResponseBody ResponseEntity> authMe(Principal principal) throws NullEmailException { diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/builders/model/models/PrincipalBuilder.java b/dmp-backend/web/src/main/java/eu/eudat/logic/builders/model/models/PrincipalBuilder.java index 133d5e179..35ce34035 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/builders/model/models/PrincipalBuilder.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/builders/model/models/PrincipalBuilder.java @@ -4,6 +4,7 @@ import eu.eudat.logic.builders.Builder; import eu.eudat.models.data.security.Principal; import eu.eudat.types.Authorities; +import java.time.Instant; import java.util.Date; import java.util.Set; import java.util.UUID; @@ -22,6 +23,8 @@ public class PrincipalBuilder extends Builder { private String culture; private String language; private String timezone; + private String zenodoToken; + private Instant zenodoDuration; public PrincipalBuilder id(UUID id) { this.id = id; @@ -68,6 +71,16 @@ public class PrincipalBuilder extends Builder { return this; } + public PrincipalBuilder zenodoToken(String zenodoToken) { + this.zenodoToken = zenodoToken; + return this; + } + + public PrincipalBuilder zenodoDuration(Instant zenodoDuration) { + this.zenodoDuration = zenodoDuration; + return this; + } + @Override public Principal build() { Principal principal = new Principal(); @@ -80,6 +93,8 @@ public class PrincipalBuilder extends Builder { principal.setCulture(culture); principal.setLanguage(language); principal.setTimezone(timezone); + principal.setZenodoToken(zenodoToken); + principal.setZenodoDuration(zenodoDuration); return principal; } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java index c8bc4928d..3883eb865 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/managers/DataManagementPlanManager.java @@ -1561,6 +1561,7 @@ public class DataManagementPlanManager { if (dmp.getDoi() != null) throw new Exception("DMP already has a DOI"); + String zenodoToken = principal.getZenodoToken() != null && !principal.getZenodoToken().isEmpty() ? principal.getZenodoToken() : this.environment.getProperty("zenodo.access_token"); // First step, post call to Zenodo, to create the entry. RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); @@ -1585,34 +1586,34 @@ public class DataManagementPlanManager { String previousDOI = this.getPreviousDOI(dmp.getGroupId(), dmp.getId()); try { if (previousDOI == null) { - String createUrl = this.environment.getProperty("zenodo.url") + "deposit/depositions" + "?access_token=" + this.environment.getProperty("zenodo.access_token"); + String createUrl = this.environment.getProperty("zenodo.url") + "deposit/depositions" + "?access_token=" + zenodoToken; createResponse = restTemplate.postForEntity(createUrl, request, Map.class).getBody(); links = (LinkedHashMap) createResponse.get("links"); } else { //It requires more than one step to create a new version //First, get the deposit related to the concept DOI - String listUrl = this.environment.getProperty("zenodo.url") + "deposit/depositions" + "?q=conceptdoi:\"" + previousDOI + "\"&access_token=" + this.environment.getProperty("zenodo.access_token"); + String listUrl = this.environment.getProperty("zenodo.url") + "deposit/depositions" + "?q=conceptdoi:\"" + previousDOI + "\"&access_token=" + zenodoToken; ResponseEntity listResponses = restTemplate.getForEntity(listUrl, Map[].class); createResponse = listResponses.getBody()[0]; links = (LinkedHashMap) createResponse.get("links"); //Second, make the new version (not in the links?) - String newVersionUrl = links.get("self") + "/actions/newversion" + "?access_token=" + this.environment.getProperty("zenodo.access_token"); + String newVersionUrl = links.get("self") + "/actions/newversion" + "?access_token=" + zenodoToken; createResponse = restTemplate.postForObject(newVersionUrl, null, Map.class); links = (LinkedHashMap) createResponse.get("links"); //Third, get the new deposit - String latestDraftUrl = links.get("latest_draft") + "?access_token=" + this.environment.getProperty("zenodo.access_token"); + String latestDraftUrl = links.get("latest_draft") + "?access_token=" + zenodoToken; createResponse = restTemplate.getForObject(latestDraftUrl, Map.class); links = (LinkedHashMap) createResponse.get("links"); //At this point it might fail to perform the next requests so enclose them with try catch try { //Forth, update the new deposit's metadata - String updateUrl = links.get("self") + "?access_token=" + this.environment.getProperty("zenodo.access_token"); + String updateUrl = links.get("self") + "?access_token=" + zenodoToken; restTemplate.put(updateUrl, request); //And finally remove pre-existing files from it - String fileListUrl = links.get("self") + "/files" + "?access_token=" + this.environment.getProperty("zenodo.access_token"); + String fileListUrl = links.get("self") + "/files" + "?access_token=" + zenodoToken; ResponseEntity fileListResponse = restTemplate.getForEntity(fileListUrl, Map[].class); for (Map file : fileListResponse.getBody()) { - String fileDeleteUrl = links.get("self") + "/files/" + file.get("id") + "?access_token=" + this.environment.getProperty("zenodo.access_token"); + String fileDeleteUrl = links.get("self") + "/files/" + file.get("id") + "?access_token=" + zenodoToken; restTemplate.delete(fileDeleteUrl); } }catch (Exception e) { diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProvider.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProvider.java new file mode 100644 index 000000000..b14c3a963 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProvider.java @@ -0,0 +1,8 @@ +package eu.eudat.logic.security.customproviders.Zenodo; + +import eu.eudat.logic.security.validators.orcid.helpers.ORCIDResponseToken; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoResponseToken; + +public interface ZenodoCustomProvider { + ZenodoResponseToken getAccessToken(String code, String clientId, String clientSecret, String redirectUri); +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProviderImpl.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProviderImpl.java new file mode 100644 index 000000000..b527af2fe --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoCustomProviderImpl.java @@ -0,0 +1,72 @@ +package eu.eudat.logic.security.customproviders.Zenodo; + +import com.fasterxml.jackson.databind.ObjectMapper; +import eu.eudat.logic.security.validators.orcid.helpers.ORCIDResponseToken; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoResponseToken; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +@Component("ZenodoCustomProvider") +public class ZenodoCustomProviderImpl implements ZenodoCustomProvider { + private static final Logger logger = LoggerFactory.getLogger(ZenodoCustomProviderImpl.class); + + private Environment environment; + + @Autowired + public ZenodoCustomProviderImpl(Environment environment) { + this.environment = environment; + } + + @Override + public ZenodoResponseToken getAccessToken(String code, String clientId, String clientSecret, String redirectUri) { + RestTemplate restTemplate = new RestTemplate(); + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON)); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + + MultiValueMap map = new LinkedMultiValueMap<>(); + map.add("client_id", clientId); + map.add("client_secret", clientSecret); + map.add("grant_type", "authorization_code"); + map.add("code", code); + map.add("redirect_uri", redirectUri); + HttpEntity> request = new HttpEntity<>(map, headers); + + try { + Map values = restTemplate.postForObject(this.environment.getProperty("zenodo.login.access_token_url"), request, Map.class); + ZenodoResponseToken zenodoResponseToken = new ZenodoResponseToken(); + Map user = (Map) values.get("user"); + zenodoResponseToken.setUserId((String) user.get("id")); + zenodoResponseToken.setEmail((String) user.get("email")); + zenodoResponseToken.setExpiresIn((Integer) values.get("expires_in")); + zenodoResponseToken.setAccessToken((String) values.get("access_token")); + + return zenodoResponseToken; + } catch (HttpClientErrorException ex) { + logger.error(ex.getResponseBodyAsString(), ex); + } + + return null; + } + + private HttpHeaders createBearerAuthHeaders(String accessToken) { + return new HttpHeaders() {{ + String authHeader = "Bearer " + accessToken; + set("Authorization", authHeader); + }}; + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoUser.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoUser.java new file mode 100644 index 000000000..cbcb95928 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/customproviders/Zenodo/ZenodoUser.java @@ -0,0 +1,49 @@ +package eu.eudat.logic.security.customproviders.Zenodo; + +import java.util.Map; + +public class ZenodoUser { + private String userId; + private String email; + private String accessToken; + private Integer expiresIn; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getEmail() { + return email; + } + public void setEmail(String email) { + this.email = email; + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } + + public ZenodoUser getZenodoUser(Object data) { + this.userId = (String) ((Map) data).get("userId"); + this.email = (String) ((Map) data).get("email"); + this.accessToken = (String) ((Map) data).get("accessToken"); + this.expiresIn = (Integer) ((Map) data).get("expiresIn"); + return this; + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/TokenValidatorFactoryImpl.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/TokenValidatorFactoryImpl.java index 9b594c96e..c079d24a5 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/TokenValidatorFactoryImpl.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/TokenValidatorFactoryImpl.java @@ -6,6 +6,7 @@ import eu.eudat.logic.security.customproviders.ConfigurableProvider.Configurable import eu.eudat.logic.security.customproviders.LinkedIn.LinkedInCustomProvider; import eu.eudat.logic.security.customproviders.ORCID.ORCIDCustomProvider; import eu.eudat.logic.security.customproviders.OpenAIRE.OpenAIRECustomProvider; +import eu.eudat.logic.security.customproviders.Zenodo.ZenodoCustomProvider; import eu.eudat.logic.security.validators.b2access.B2AccessTokenValidator; import eu.eudat.logic.security.validators.configurableProvider.ConfigurableProviderTokenValidator; import eu.eudat.logic.security.validators.facebook.FacebookTokenValidator; @@ -14,6 +15,7 @@ import eu.eudat.logic.security.validators.linkedin.LinkedInTokenValidator; import eu.eudat.logic.security.validators.openaire.OpenAIRETokenValidator; import eu.eudat.logic.security.validators.orcid.ORCIDTokenValidator; import eu.eudat.logic.security.validators.twitter.TwitterTokenValidator; +import eu.eudat.logic.security.validators.zenodo.ZenodoTokenValidator; import eu.eudat.logic.services.operations.authentication.AuthenticationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; @@ -23,7 +25,7 @@ import org.springframework.stereotype.Service; @Service("tokenValidatorFactory") public class TokenValidatorFactoryImpl implements TokenValidatorFactory { public enum LoginProvider { - GOOGLE(1), FACEBOOK(2), TWITTER(3), LINKEDIN(4), NATIVELOGIN(5), B2_ACCESS(6), ORCID(7), OPENAIRE(8), CONFIGURABLE(9); + GOOGLE(1), FACEBOOK(2), TWITTER(3), LINKEDIN(4), NATIVELOGIN(5), B2_ACCESS(6), ORCID(7), OPENAIRE(8), CONFIGURABLE(9), ZENODO(10); private int value; @@ -55,6 +57,8 @@ public class TokenValidatorFactoryImpl implements TokenValidatorFactory { return OPENAIRE; case 9: return CONFIGURABLE; + case 10: + return ZENODO; default: throw new RuntimeException("Unsupported LoginProvider"); } @@ -69,12 +73,15 @@ public class TokenValidatorFactoryImpl implements TokenValidatorFactory { private OpenAIRECustomProvider openAIRECustomProvider; private ConfigurableProviderCustomProvider configurableProviderCustomProvider; private ConfigLoader configLoader; + private ZenodoCustomProvider zenodoCustomProvider; @Autowired public TokenValidatorFactoryImpl( Environment environment, AuthenticationService nonVerifiedUserAuthenticationService, B2AccessCustomProvider b2AccessCustomProvider, - ORCIDCustomProvider orcidCustomProvider, LinkedInCustomProvider linkedInCustomProvider, OpenAIRECustomProvider openAIRECustomProvider, ConfigurableProviderCustomProvider configurableProviderCustomProvider, ConfigLoader configLoader) { + ORCIDCustomProvider orcidCustomProvider, LinkedInCustomProvider linkedInCustomProvider, OpenAIRECustomProvider openAIRECustomProvider, + ConfigurableProviderCustomProvider configurableProviderCustomProvider, ConfigLoader configLoader, + ZenodoCustomProvider zenodoCustomProvider) { this.environment = environment; this.nonVerifiedUserAuthenticationService = nonVerifiedUserAuthenticationService; this.b2AccessCustomProvider = b2AccessCustomProvider; @@ -83,6 +90,7 @@ public class TokenValidatorFactoryImpl implements TokenValidatorFactory { this.openAIRECustomProvider = openAIRECustomProvider; this.configurableProviderCustomProvider = configurableProviderCustomProvider; this.configLoader = configLoader; + this.zenodoCustomProvider = zenodoCustomProvider; } public TokenValidator getProvider(LoginProvider provider) { @@ -103,6 +111,8 @@ public class TokenValidatorFactoryImpl implements TokenValidatorFactory { return new OpenAIRETokenValidator(this.environment, this.nonVerifiedUserAuthenticationService, this.openAIRECustomProvider); case CONFIGURABLE: return new ConfigurableProviderTokenValidator(this.configurableProviderCustomProvider, this.nonVerifiedUserAuthenticationService, this.configLoader); + case ZENODO: + return new ZenodoTokenValidator(this.environment, this.nonVerifiedUserAuthenticationService, this.zenodoCustomProvider); default: throw new RuntimeException("Login Provider Not Implemented"); } diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/ZenodoTokenValidator.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/ZenodoTokenValidator.java new file mode 100644 index 000000000..2708a96b2 --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/ZenodoTokenValidator.java @@ -0,0 +1,57 @@ +package eu.eudat.logic.security.validators.zenodo; + +import eu.eudat.exceptions.security.NonValidTokenException; +import eu.eudat.exceptions.security.NullEmailException; +import eu.eudat.logic.security.customproviders.ORCID.ORCIDCustomProvider; +import eu.eudat.logic.security.customproviders.ORCID.ORCIDUser; +import eu.eudat.logic.security.customproviders.Zenodo.ZenodoCustomProvider; +import eu.eudat.logic.security.customproviders.Zenodo.ZenodoUser; +import eu.eudat.logic.security.validators.TokenValidator; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoRequest; +import eu.eudat.logic.security.validators.zenodo.helpers.ZenodoResponseToken; +import eu.eudat.logic.services.operations.authentication.AuthenticationService; +import eu.eudat.models.data.login.LoginInfo; +import eu.eudat.models.data.loginprovider.LoginProviderUser; +import eu.eudat.models.data.security.Principal; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +@Component("zenodoTokenValidator") +public class ZenodoTokenValidator implements TokenValidator { + + private ZenodoCustomProvider zenodoCustomProvider; + private Environment environment; + private AuthenticationService nonVerifiedUserAuthenticationService; + + @Autowired + public ZenodoTokenValidator(Environment environment, AuthenticationService nonVerifiedUserAuthenticationService, ZenodoCustomProvider zenodoCustomProvider) { + this.environment = environment; + this.nonVerifiedUserAuthenticationService = nonVerifiedUserAuthenticationService; + this.zenodoCustomProvider = zenodoCustomProvider; + } + + @Override + public Principal validateToken(LoginInfo credentials) throws NonValidTokenException, IOException, GeneralSecurityException, NullEmailException { + ZenodoUser zenodoUser = new ZenodoUser().getZenodoUser(credentials.getData()); + LoginProviderUser user = new LoginProviderUser(); + user.setId(zenodoUser.getUserId()); + user.setName(zenodoUser.getEmail()); + user.setEmail(zenodoUser.getEmail()); + user.setZenodoId(zenodoUser.getAccessToken()); + user.setZenodoExpire(zenodoUser.getExpiresIn()); + user.setProvider(credentials.getProvider()); + user.setSecret(credentials.getTicket()); + return this.nonVerifiedUserAuthenticationService.Touch(user); + } + + public ZenodoResponseToken getAccessToken(ZenodoRequest zenodoRequest) { + return this.zenodoCustomProvider.getAccessToken(zenodoRequest.getCode() + , this.environment.getProperty("zenodo.login.client_id") + , this.environment.getProperty("zenodo.login.client_secret") + , this.environment.getProperty("zenodo.login.redirect_uri")); + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoRequest.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoRequest.java new file mode 100644 index 000000000..c1f4ea32a --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoRequest.java @@ -0,0 +1,12 @@ +package eu.eudat.logic.security.validators.zenodo.helpers; + +public class ZenodoRequest { + private String code; + + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoResponseToken.java b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoResponseToken.java new file mode 100644 index 000000000..5b437cdfb --- /dev/null +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/security/validators/zenodo/helpers/ZenodoResponseToken.java @@ -0,0 +1,38 @@ +package eu.eudat.logic.security.validators.zenodo.helpers; + +public class ZenodoResponseToken { + private String userId; + private String email; + private Integer expiresIn; + private String accessToken; + + public String getUserId() { + return userId; + } + public void setUserId(String userId) { + this.userId = userId; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } + + public String getAccessToken() { + return accessToken; + } + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } +} diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/AbstractAuthenticationService.java b/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/AbstractAuthenticationService.java index cb7506ab6..fc09056fa 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/AbstractAuthenticationService.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/AbstractAuthenticationService.java @@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; import org.springframework.transaction.annotation.Transactional; +import java.time.Instant; import java.util.*; public abstract class AbstractAuthenticationService implements AuthenticationService { @@ -132,7 +133,7 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer userInfo = this.apiContext.getOperationsContext().getBuilderFactory().getBuilder(UserInfoBuilder.class) .name(profile.getName()).verified_email(profile.getIsVerified()) .email(profile.getEmail()).created(new Date()).lastloggedin(new Date()) - .additionalinfo("{\"data\":{\"avatar\":{\"url\":\"" + profile.getAvatarUrl() + "\"}}}") + .additionalinfo("{\"data\":{\"avatar\":{\"url\":\"" + profile.getAvatarUrl() + "\"}},{\"zenodoToken\":\"" + profile.getZenodoId() + "\", \"expirationDate\": \"" + Instant.now().plusSeconds(profile.getZenodoExpire()).toEpochMilli() + "\"}") .authorization_level((short) 1).usertype((short) 1) .build(); @@ -149,7 +150,15 @@ public abstract class AbstractAuthenticationService implements AuthenticationSer } else { Map additionalInfo = userInfo.getAdditionalinfo() != null ? new JSONObject(userInfo.getAdditionalinfo()).toMap() : new HashMap<>(); - additionalInfo.put("avatarUrl", profile.getAvatarUrl()); + if (profile.getAvatarUrl() != null && !profile.getAvatarUrl().isEmpty() && !profile.getAvatarUrl().equals("null")) { + additionalInfo.put("avatarUrl", profile.getAvatarUrl()); + } + if (profile.getZenodoId() != null && !profile.getZenodoId().isEmpty() && !profile.getZenodoId().equals("null")) { + additionalInfo.put("zenodoToken", profile.getZenodoId()); + } + if (profile.getZenodoExpire() != null) { + additionalInfo.put("expirationDate", Instant.now().plusSeconds(profile.getZenodoExpire()).toEpochMilli()); + } userInfo.setLastloggedin(new Date()); userInfo.setAdditionalinfo(new JSONObject(additionalInfo).toString()); Set credentials = userInfo.getCredentials(); diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/NonVerifiedUserEmailAuthenticationService.java b/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/NonVerifiedUserEmailAuthenticationService.java index 62c5f60ae..159bbb235 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/NonVerifiedUserEmailAuthenticationService.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/NonVerifiedUserEmailAuthenticationService.java @@ -11,6 +11,7 @@ import eu.eudat.types.Authorities; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; +import java.time.Instant; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -33,6 +34,18 @@ public class NonVerifiedUserEmailAuthenticationService extends AbstractAuthentic } catch (Exception e) { avatarUrl = ""; } + String zenodoToken; + try { + zenodoToken = user.getAdditionalinfo() != null ? new ObjectMapper().readTree(user.getAdditionalinfo()).get("zenodoToken").asText() : ""; + } catch (Exception e) { + zenodoToken = ""; + } + Instant zenodoDuration; + try { + zenodoDuration = user.getAdditionalinfo() != null ? Instant.ofEpochMilli(new ObjectMapper().readTree(user.getAdditionalinfo()).get("expirationDate").asLong()) : Instant.now(); + } catch (Exception e) { + zenodoDuration = Instant.now(); + } String culture; try { culture = user.getAdditionalinfo() != null ? new ObjectMapper().readTree(user.getAdditionalinfo()).get("culture").get("name").asText() : ""; @@ -58,6 +71,8 @@ public class NonVerifiedUserEmailAuthenticationService extends AbstractAuthentic .culture(culture) .language(language) .timezone(timezone) + .zenodoToken(zenodoToken) + .zenodoDuration(zenodoDuration) .build(); List userRoles = apiContext.getOperationsContext().getDatabaseRepository().getUserRoleDao().getUserRoles(user); diff --git a/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/VerifiedUserAuthenticationService.java b/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/VerifiedUserAuthenticationService.java index acbb3dc86..c03e81934 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/VerifiedUserAuthenticationService.java +++ b/dmp-backend/web/src/main/java/eu/eudat/logic/services/operations/authentication/VerifiedUserAuthenticationService.java @@ -20,6 +20,7 @@ import org.json.JSONObject; import org.springframework.core.env.Environment; import org.springframework.stereotype.Service; +import java.time.Instant; import java.util.*; @@ -42,6 +43,18 @@ public class VerifiedUserAuthenticationService extends AbstractAuthenticationSer } catch (Exception e) { avatarUrl = ""; } + String zenodoToken; + try { + zenodoToken = user.getAdditionalinfo() != null ? new ObjectMapper().readTree(user.getAdditionalinfo()).get("zenodoToken").asText() : ""; + } catch (Exception e) { + zenodoToken = ""; + } + Instant zenodoDuration; + try { + zenodoDuration = user.getAdditionalinfo() != null ? Instant.ofEpochMilli(new ObjectMapper().readTree(user.getAdditionalinfo()).get("expirationDate").asLong()) : Instant.now(); + } catch (Exception e) { + zenodoDuration = Instant.now(); + } String culture; try { culture = user.getAdditionalinfo() != null ? new ObjectMapper().readTree(user.getAdditionalinfo()).get("culture").get("name").asText() : ""; @@ -67,6 +80,8 @@ public class VerifiedUserAuthenticationService extends AbstractAuthenticationSer .culture(culture) .language(language) .timezone(timezone) + .zenodoToken(zenodoToken) + .zenodoDuration(zenodoDuration) .build(); List userRoles = apiContext.getOperationsContext().getDatabaseRepository().getUserRoleDao().getUserRoles(user); diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/loginprovider/LoginProviderUser.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/loginprovider/LoginProviderUser.java index e1b0a6d41..13f5b9371 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/data/loginprovider/LoginProviderUser.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/loginprovider/LoginProviderUser.java @@ -11,6 +11,8 @@ public class LoginProviderUser { private String avatarUrl; private boolean isVerified; private TokenValidatorFactoryImpl.LoginProvider provider; + private String zenodoId; + private Integer zenodoExpire; public String getId() { return id; @@ -67,4 +69,20 @@ public class LoginProviderUser { public void setAvatarUrl(String avatarUrl) { this.avatarUrl = avatarUrl; } + + public String getZenodoId() { + return zenodoId; + } + + public void setZenodoId(String zenodoId) { + this.zenodoId = zenodoId; + } + + public Integer getZenodoExpire() { + return zenodoExpire; + } + + public void setZenodoExpire(Integer zenodoExpire) { + this.zenodoExpire = zenodoExpire; + } } diff --git a/dmp-backend/web/src/main/java/eu/eudat/models/data/security/Principal.java b/dmp-backend/web/src/main/java/eu/eudat/models/data/security/Principal.java index 5399b5466..f0e017e84 100644 --- a/dmp-backend/web/src/main/java/eu/eudat/models/data/security/Principal.java +++ b/dmp-backend/web/src/main/java/eu/eudat/models/data/security/Principal.java @@ -3,6 +3,7 @@ package eu.eudat.models.data.security; import com.fasterxml.jackson.annotation.JsonIgnore; import eu.eudat.types.Authorities; +import java.time.Instant; import java.util.*; import java.util.stream.Collectors; @@ -17,6 +18,8 @@ public class Principal { private String culture; private String language; private String timezone; + private String zenodoToken; + private Instant zenodoDuration; public UUID getId() { return id; @@ -86,6 +89,22 @@ public class Principal { this.timezone = timezone; } + public String getZenodoToken() { + return zenodoToken; + } + + public void setZenodoToken(String zenodoToken) { + this.zenodoToken = zenodoToken; + } + + public Instant getZenodoDuration() { + return zenodoDuration; + } + + public void setZenodoDuration(Instant zenodoDuration) { + this.zenodoDuration = zenodoDuration; + } + @JsonIgnore public Set getAuthz() { return this.authorities; diff --git a/dmp-backend/web/src/main/resources/config/application-devel.properties b/dmp-backend/web/src/main/resources/config/application-devel.properties index 59d292b11..7051f6667 100644 --- a/dmp-backend/web/src/main/resources/config/application-devel.properties +++ b/dmp-backend/web/src/main/resources/config/application-devel.properties @@ -69,6 +69,10 @@ conf_email.subject=OpenDMP email confirmation #############ZENODO CONFIGURATIONS######### zenodo.url=https://sandbox.zenodo.org/api/ zenodo.access_token= +zenodo.login.access_token_url=https://sandbox.zenodo.org/oauth/token +zenodo.login.client_id= +zenodo.login.client_secret= +zenodo.login.redirect_uri=http://localhost:4200/login/external/zenodo #############CONTACT EMAIL CONFIGURATIONS######### contact_email.mail= diff --git a/dmp-frontend/src/app/core/common/enum/auth-provider.ts b/dmp-frontend/src/app/core/common/enum/auth-provider.ts index b8b70efd8..11c36b2a9 100644 --- a/dmp-frontend/src/app/core/common/enum/auth-provider.ts +++ b/dmp-frontend/src/app/core/common/enum/auth-provider.ts @@ -7,5 +7,6 @@ export enum AuthProvider { B2Access = 6, ORCID = 7, OpenAire = 8, - Configurable = 9 + Configurable = 9, + Zenodo = 10 } diff --git a/dmp-frontend/src/app/core/model/configuration-models/login-providers.model.ts b/dmp-frontend/src/app/core/model/configuration-models/login-providers.model.ts index 8c7751e2c..15474bdce 100644 --- a/dmp-frontend/src/app/core/model/configuration-models/login-providers.model.ts +++ b/dmp-frontend/src/app/core/model/configuration-models/login-providers.model.ts @@ -42,6 +42,11 @@ export class LoginProviders { return this._openAireConfiguration; } + private _zenodoConfiguration: LoginConfiguration; + get zenodoConfiguration(): LoginConfiguration { + return this._zenodoConfiguration; + } + public static parseValue(value: any): LoginProviders { const obj: LoginProviders = new LoginProviders(); obj._enabled = value.enabled; @@ -52,6 +57,7 @@ export class LoginProviders { obj._b2accessConfiguration = value.b2accessConfiguration; obj._orcidConfiguration = value.orcidConfiguration; obj._openAireConfiguration = value.openAireConfiguration; + obj._zenodoConfiguration = value.zenodoConfiguration; return obj; } } diff --git a/dmp-frontend/src/app/core/model/zenodo/zenodo-token.model.ts b/dmp-frontend/src/app/core/model/zenodo/zenodo-token.model.ts new file mode 100644 index 000000000..0ddb251d6 --- /dev/null +++ b/dmp-frontend/src/app/core/model/zenodo/zenodo-token.model.ts @@ -0,0 +1,6 @@ +export class ZenodoToken { + userId: string; + expiresIn: number; + accessToken:string; + email: string; +} diff --git a/dmp-frontend/src/app/ui/auth/login/img/zenodo-white-200.png b/dmp-frontend/src/app/ui/auth/login/img/zenodo-white-200.png new file mode 100644 index 0000000000000000000000000000000000000000..5fb49be6e056112979c675e44fe46bd7e908390d GIT binary patch literal 2383 zcmb7`c{tRI8pnq$!$CF2zBgjx$S%7qBW6fuqB@kNED2d_j3sNvB};>d;>_uXW-Nm# zTTH1OoGCP7Dl{hhG6rW#V@$4l?tSk4`~LAR&-1>|`+c7GpYP`%UY@SflFE`G5J(!~ z21oDL!Ts<8i|yx&QPwT{MJd7MLc-bT&;(3STnNZ9IQnvkCL$^*ECd}A6pW1>3b6%& zz?%rTqj%z`wTBXSWL;!)A~2T>93328`sK)FAUs&i1@%r7C$5fysvm>GrSs0e0HbE_ z=8Ch7P_iR8d^UuQ)AOL2LU5E(;*3P6yH|#f)pSUTq2NwQJLwsz9yVdLSH7FUZvIi< zT_3=B=VPvY-{X`Jh+Fyv0!jx7frNxX!Xh9MFbI4IbVw7V=>T#_|9kMi?(dbvVHU6n zROK%N+b_3e^t;LvB*JZ$UFi}1F?8(;BT@a%GH&2iKXD0J?^cI*@p++s$xj?X;18sa z-xP#J($l9aqm=elTx9;Mh8`GDxGe|h=zfpU+Ok1sg$76v@$U;0(sz3^yxn*7a3hf( z)_Dnsh)t47Ngv|vmT{^nenU6HVbrMxF6C;f43%FZBhPmKZLxqh_F^7}bF z$!!vva=zFgwrAK55q(!#s=6oj1hE{bd5joyo=vt7kA?eV@cvqAiyu?NN)45CwF(C3 zaPwPAjMq~yf_@w=XdUEd0ZHP_n&a1(;^_H>i(UBllhEQYo1M7y;rEdzjcFavE`}*s zdVM>U{()$*A74nHhAIa{}NXj>mC(Z*$!ynA2{wvpwoxfjBuS z|3=Tg2K4ZxO^Qn6VAdBgL{@7bt1~K&@XSi}ullL)Cx@6g{BT~U73^^uE@|Y*S5u&& zOrTQxbG8cmfYz3AHuu5qo*(^iPQny+Bf~7Q>aO+O9r$;3Lr0XsK*jLtSvrI+Pqzn# zfw9ZY(~5Ms+?_RZ;~MVmgkw*1zCxnaR?Em&o=MiW1{1E~eiwK;YxQU^7v|1Z78e?? zO(K+Yp!} z?2@+}z?9KG%KanJj3W+opR>G|kr&Eeq!Dj(^j$~dmYoW(Jb1)m-D9xyT$#=HsLj17 zi*Vb{V5_0}u zWBwv+V+v|_i$yrobXc=%Y-~ez0XFADf2tIkoH^3Ed~MkEB+ql;`g+=9T5KA9w!L;~ zQc(!6vRisT>H~(ln+&i7aC>C&QAUgYy1?6s+1Xb=FG0p)t5a?;f;yrt{0N&Qg5oW` zUt(6w`Gz`8t({gB_aXz)n^8#FV=JJp!i45fz9fCzc)WUG<3^TUSKbY&Wt4MjswNfb zwI*aT&>}#}d29&%cJ)9ufBoQCWLhQ{LL&ZF@q8*MM^E-lklOmpD-NiW(~J6(H<>pH z7TQXS%`Jsk?^q{oy7&90%m z)!Z%-*0yr@kh5*q(nk>m`|$A<D7v&@O#TTmOCr{J8|WLN$?@lv!%CwJ5uyKPD$@&yrR5PN@0s-wL(F~tmD`1 z)3hfF`0GZtkv~Sg%Oe;6xN~ktV+1>1j9WdPT2or-q*#4`LL;w3(n6!ZTD3^jx0Ve3-#RzlmuDx08Tubx@vej&O;+QWRiR*^R-9eO?8MY8ed;k~uVt z2~b1K42ey`wZ{umny!2nBkmP~W|VGT0!$qToODstyO8zZAr3W%>)cOqa@o5#<3XHg zEW7!dsHe!g2zfUepxv8XCnL2kl6ap>keskxV4AKOn)GQs=}0`!+CGZrjC_9!!;geu z^&S5hY;z9xQrqdFp|{kOwt8XO%nWLC@{GI6>qV?oL+yaeP>E3i1{9J5i@!7xrd{ic zoQot$Mae28wssB?qGn6^4|a{p-E|kVYt^T-d65q`6=EVXXA>*jI~u9jOQ~i<-k8ITjo5hQ%2eCfa=+fG~DkTi%UoD%n=EKU7uWp6h z;asCnquG?r>6!$M@B)|mSC5^3_5HKsDfZc5ej~b!e;KTe)_d! literal 0 HcmV?d00001 diff --git a/dmp-frontend/src/app/ui/auth/login/login.component.html b/dmp-frontend/src/app/ui/auth/login/login.component.html index d8be9c88b..dd1c8fc9e 100644 --- a/dmp-frontend/src/app/ui/auth/login/login.component.html +++ b/dmp-frontend/src/app/ui/auth/login/login.component.html @@ -54,6 +54,12 @@ +