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

Authorization for Downloads of restricted Bitstreams: Short lived token endpoint #2783

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -16,10 +16,13 @@
import org.dspace.app.rest.converter.EPersonConverter;
import org.dspace.app.rest.link.HalLinkService;
import org.dspace.app.rest.model.AuthenticationStatusRest;
import org.dspace.app.rest.model.AuthenticationTokenRest;
import org.dspace.app.rest.model.AuthnRest;
import org.dspace.app.rest.model.EPersonRest;
import org.dspace.app.rest.model.hateoas.AuthenticationStatusResource;
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
import org.dspace.app.rest.model.hateoas.AuthnResource;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.projection.Projection;
import org.dspace.app.rest.security.RestAuthenticationService;
import org.dspace.app.rest.utils.ContextUtil;
Expand All @@ -32,6 +35,7 @@
import org.springframework.hateoas.Link;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
Expand Down Expand Up @@ -118,6 +122,30 @@ public ResponseEntity login(HttpServletRequest request, @RequestParam(name = "us
"valid.");
}

/**
* This method will generate a short lived token to be used for bitstream downloads among other things.
*
* curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
*
* Example:
* <pre>
* {@code
* curl -v -X POST https://{dspace-server.url}/api/authn/shortlivedtokens -H "Authorization: Bearer eyJhbG...COdbo"
* }
* </pre>
* @param request The StandardMultipartHttpServletRequest
* @return The created short lived token
*/
@PreAuthorize("hasAuthority('AUTHENTICATED')")
@RequestMapping(value = "/shortlivedtokens", method = RequestMethod.POST)
public AuthenticationTokenResource shortLivedToken(HttpServletRequest request) {
Projection projection = utils.obtainProjection();
AuthenticationToken shortLivedToken =
restAuthenticationService.getShortLivedAuthenticationToken(ContextUtil.obtainContext(request), request);
AuthenticationTokenRest authenticationTokenRest = converter.toRest(shortLivedToken, projection);
return converter.toResource(authenticationTokenRest);
}

@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.PUT, RequestMethod.PATCH,
RequestMethod.DELETE })
public ResponseEntity login() {
Expand Down
@@ -0,0 +1,31 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.converter;

import org.dspace.app.rest.model.AuthenticationTokenRest;
import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.app.rest.projection.Projection;
import org.springframework.stereotype.Component;

/**
* This is the converter from the AuthenticationToken to the REST data model
*/
@Component
public class AuthenticationTokenConverter implements DSpaceConverter<AuthenticationToken, AuthenticationTokenRest> {
@Override
public AuthenticationTokenRest convert(AuthenticationToken modelObject, Projection projection) {
AuthenticationTokenRest token = new AuthenticationTokenRest();
token.setToken(modelObject.getToken());
return token;
}

@Override
public Class<AuthenticationToken> getModelClass() {
return AuthenticationToken.class;
}
}
@@ -0,0 +1,42 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.link;

import java.util.LinkedList;

import org.dspace.app.rest.AuthenticationRestController;
import org.dspace.app.rest.model.hateoas.AuthenticationTokenResource;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Link;
import org.springframework.stereotype.Component;

/**
* This class adds the self link to the AuthenticationTokenResource.
*/
@Component
public class AuthenticationTokenHalLinkFactory
extends HalLinkFactory<AuthenticationTokenResource, AuthenticationRestController> {

@Override
protected void addLinks(AuthenticationTokenResource halResource, Pageable pageable, LinkedList<Link> list)
throws Exception {

list.add(buildLink(IanaLinkRelations.SELF.value(), getMethodOn().shortLivedToken(null)));
}

@Override
protected Class<AuthenticationRestController> getControllerClass() {
return AuthenticationRestController.class;
}

@Override
protected Class<AuthenticationTokenResource> getResourceClass() {
return AuthenticationTokenResource.class;
}
}
Expand Up @@ -18,7 +18,7 @@ public class AuthenticationStatusRest extends BaseObjectRest<Integer> {
private boolean authenticated;

public static final String NAME = "status";
public static final String CATEGORY = "authn";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;

@Override
public String getCategory() {
Expand Down
@@ -0,0 +1,44 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model;

import org.dspace.app.rest.RestResourceController;

/**
* The authentication token REST HAL Resource. The HAL Resource wraps the REST Resource
* adding support for the links and embedded resources
*/
public class AuthenticationTokenRest extends RestAddressableModel {
public static final String NAME = "shortlivedtoken";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;

private String token;

@Override
public String getCategory() {
return CATEGORY;
}

@Override
public Class getController() {
return RestResourceController.class;
}

@Override
public String getType() {
return NAME;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}
}
Expand Up @@ -18,7 +18,7 @@
public class AuthnRest extends BaseObjectRest<Integer> {

public static final String NAME = "authn";
public static final String CATEGORY = "authn";
public static final String CATEGORY = RestAddressableModel.AUTHENTICATION;

public String getCategory() {
return CATEGORY;
Expand Down
Expand Up @@ -32,6 +32,7 @@ public interface RestModel extends Serializable {
public static final String WORKFLOW = "workflow";
public static final String AUTHORIZATION = "authz";
public static final String VERSIONING = "versioning";
public static final String AUTHENTICATION = "authn";

public String getType();

Expand Down
@@ -0,0 +1,20 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.hateoas;

import org.dspace.app.rest.model.AuthenticationTokenRest;

/**
* Token resource, wraps the AuthenticationToken object
*/
public class AuthenticationTokenResource extends HALResource<AuthenticationTokenRest> {

public AuthenticationTokenResource(AuthenticationTokenRest content) {
super(content);
}
}
@@ -0,0 +1,28 @@
/**
* The contents of this file are subject to the license and copyright
* detailed in the LICENSE and NOTICE files at the root of the source
* tree and available online at
*
* http://www.dspace.org/license/
*/
package org.dspace.app.rest.model.wrapper;

/**
* This class represents an authentication token. It acts as a wrapper for a String object to differentiate between
* actual Strings and AuthenticationToken
*/
public class AuthenticationToken {
private String token;

public AuthenticationToken(String token) {
this.token = token;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}
}
Expand Up @@ -10,7 +10,6 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dspace.app.rest.security.jwt.JWTTokenHandler;
import org.dspace.app.rest.utils.ContextUtil;
import org.dspace.core.Context;
import org.slf4j.Logger;
Expand All @@ -29,7 +28,7 @@
@Component
public class CustomLogoutHandler implements LogoutHandler {

private static final Logger log = LoggerFactory.getLogger(JWTTokenHandler.class);
private static final Logger log = LoggerFactory.getLogger(CustomLogoutHandler.class);

@Autowired
private RestAuthenticationService restAuthenticationService;
Expand Down
Expand Up @@ -11,6 +11,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.dspace.app.rest.model.wrapper.AuthenticationToken;
import org.dspace.authenticate.service.AuthenticationService;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
Expand All @@ -28,6 +29,14 @@ public interface RestAuthenticationService {
void addAuthenticationDataForUser(HttpServletRequest request, HttpServletResponse response,
DSpaceAuthentication authentication, boolean addCookie) throws IOException;

/**
* Retrieve a short lived authentication token, this can be used (among other things) for file downloads
* @param context the DSpace context
* @param request The current client request
* @return An AuthenticationToken that contains a string with the token
*/
AuthenticationToken getShortLivedAuthenticationToken(Context context, HttpServletRequest request);

EPerson getAuthenticatedEPerson(HttpServletRequest request, Context context);

boolean hasAuthenticationData(HttpServletRequest request);
Expand Down