Skip to content
package it.inaf.ia2.gms.client.call;
import it.inaf.ia2.gms.client.BaseGmsClientTest;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class RemoveMemberTest extends BaseGmsClientTest {
@BeforeEach
@Override
public void init() {
super.init();
}
@Test
public void testRemoveMember() {
CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(204));
when(httpClient.sendAsync(any(), any())).thenReturn(response);
gmsClient.removeMember("LBT.INAF", "user");
verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/membership/LBT.INAF?user_id=user"), any());
}
}
package it.inaf.ia2.gms.client.call;
import it.inaf.ia2.gms.client.BaseGmsClientTest;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class RemovePermissionTest extends BaseGmsClientTest {
@BeforeEach
@Override
public void init() {
super.init();
}
@Test
public void testRemovePermission() {
CompletableFuture response = CompletableFuture.completedFuture(getMockedResponse(204));
when(httpClient.sendAsync(any(), any())).thenReturn(response);
gmsClient.removePermission("LBT.INAF", "user");
verify(httpClient, times(1)).sendAsync(endpointEq("DELETE", "ws/jwt/permission/LBT.INAF?user_id=user"), any());
}
}
......@@ -6,7 +6,7 @@
<span v-if="group.active">{{group.groupName}}</span>
</li>
</ol>
<a v-if="currentGroup" :href="'group/status/' + currentGroup.groupId" :download="currentGroup.groupName + '.csv'" id="csv-status-download" title="Download CSV">
<a v-if="currentGroup" :href="'group/status?groupId=' + currentGroup.groupId" :download="currentGroup.groupName + '.csv'" id="csv-status-download" title="Download CSV">
<font-awesome-icon icon="download"></font-awesome-icon>
</a>
</nav>
......
......@@ -40,7 +40,7 @@
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>AuthLib</artifactId>
<artifactId>auth-lib</artifactId>
<version>2.0.0-SNAPSHOT</version>
</dependency>
<dependency>
......@@ -68,36 +68,46 @@
</dependency>
</dependencies>
<profiles>
<profile>
<id>build-gui</id>
<build>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.7.6</version>
<configuration>
<nodeVersion>v12.6.0</nodeVersion>
<environmentVariables>
<VUE_APP_SHOW_USER_ID_IN_SEARCH>${show.user_id_in_search}</VUE_APP_SHOW_USER_ID_IN_SEARCH>
</environmentVariables>
</configuration>
<executions>
<execution>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build --prefix ../gms-ui</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
<finalName>gms</finalName>
<plugins>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.7.6</version>
<configuration>
<nodeVersion>v12.6.0</nodeVersion>
<environmentVariables>
<VUE_APP_SHOW_USER_ID_IN_SEARCH>${show.user_id_in_search}</VUE_APP_SHOW_USER_ID_IN_SEARCH>
</environmentVariables>
</configuration>
<executions>
<execution>
<goals>
<goal>install-node-and-npm</goal>
</goals>
</execution>
<execution>
<id>npm install</id>
<goals>
<goal>npm</goal>
</goals>
<configuration>
<arguments>run build --prefix ../gms-ui</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.1.0</version>
......@@ -184,4 +194,10 @@
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>ia2.snapshot</id>
<url>http://repo.ia2.inaf.it/maven/repository/snapshots</url>
</repository>
</repositories>
</project>
......@@ -2,14 +2,11 @@ package it.inaf.ia2.gms;
import it.inaf.ia2.aa.AuthConfig;
import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.UriCustomizer;
import it.inaf.ia2.aa.jwt.QueryStringBuilder;
import static it.inaf.ia2.gms.authn.ClientDbFilter.CLIENT_DB;
import it.inaf.ia2.gms.exception.BadRequestException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.gms.authn.ServletRapClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
......@@ -20,47 +17,20 @@ public class GmsApplication {
public static void main(String[] args) {
SpringApplication.run(GmsApplication.class, args);
}
AuthConfig authConfig = ServiceLocator.getInstance().getConfig();
final String defaultAuthorizationUri = authConfig.getUserAuthorizationUri();
authConfig.setAuthorizationUriCustomizer(new UriCustomizer() {
@Override
public String getBaseUri(HttpServletRequest req) {
// for a better security we should check for allowed redirects
String redirect = req.getParameter("redirect");
if (redirect != null) {
return redirect;
}
return defaultAuthorizationUri;
}
@Override
public void customizeQueryString(HttpServletRequest req, QueryStringBuilder queryStringBuilder) {
String clientDb = req.getParameter(CLIENT_DB);
if (clientDb == null) {
HttpSession session = req.getSession(false);
if (session != null) {
clientDb = (String) session.getAttribute(CLIENT_DB);
}
}
if (clientDb == null) {
throw new BadRequestException("client_db not set");
}
queryStringBuilder.param(CLIENT_DB, clientDb);
}
});
@Bean
public AuthConfig authConfig() {
return ServiceLocator.getInstance().getConfig();
}
final String defaultAccessTokenUri = authConfig.getAccessTokenUri();
@Bean
public UserManager userManager() {
return ServiceLocator.getInstance().getUserManager();
}
authConfig.setAccessTokenUriCustomizer(req -> {
String redirect = req.getParameter("token_uri");
if (redirect != null) {
return redirect;
}
return defaultAccessTokenUri;
});
@Bean
public ServletRapClient servletRapClient() {
return (ServletRapClient) ServiceLocator.getInstance().getRapClient();
}
}
package it.inaf.ia2.gms.authn;
import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.jwt.JwksClient;
import it.inaf.ia2.aa.AuthConfig;
import it.inaf.ia2.aa.UserManager;
import java.io.IOException;
import java.net.URI;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ClientDbFilter implements Filter {
public static final String CLIENT_DB = "client_db";
private String defaultJwksUri;
private JwksClient jwksClient;
private final UserManager userManager;
private final String defaultJwksUri;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
defaultJwksUri = ServiceLocator.getInstance().getConfig().getJwksUri();
jwksClient = ServiceLocator.getInstance().getJwksClient();
public ClientDbFilter(AuthConfig authConfig, UserManager userManager) {
this.userManager = userManager;
defaultJwksUri = URI.create(authConfig.getRapBaseUri()).resolve(authConfig.getJwksEndpoint()).toString();
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain fc) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String clientDb = request.getParameter(CLIENT_DB);
if (clientDb != null) {
request.getSession().setAttribute(CLIENT_DB, clientDb);
String newUrl = defaultJwksUri.replaceAll("\\?client_name=(.*)", "?client_name=" + clientDb);
jwksClient.addJwksUrl(newUrl);
userManager.addJwksUri(URI.create(newUrl));
}
fc.doFilter(req, res);
......
package it.inaf.ia2.gms.authn;
import static it.inaf.ia2.gms.authn.ClientDbFilter.CLIENT_DB;
import it.inaf.ia2.gms.exception.BadRequestException;
import it.inaf.ia2.rap.client.call.GetUserCall;
import it.inaf.ia2.rap.data.RapUser;
import java.net.URI;
import java.net.http.HttpRequest;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClientDbRapClient extends ServletRapClient {
private static final Logger LOG = LoggerFactory.getLogger(ClientDbRapClient.class);
public ClientDbRapClient(String baseUrl) {
super(baseUrl);
}
@Override
protected HttpRequest.Builder newAuthRequest(HttpRequest.Builder requestBuilder, HttpServletRequest request) {
return setClientDb(super.newClientSecretRequest(requestBuilder), request);
}
@Override
public HttpRequest.Builder newRequest(String endpoint, HttpServletRequest context) {
return setClientDb(super.newRequest(endpoint), context);
}
@Override
public HttpRequest.Builder newRequest(URI uri, HttpServletRequest context) {
return setClientDb(super.newRequest(uri), context);
}
private HttpRequest.Builder setClientDb(HttpRequest.Builder builder, HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
String clientDb = (String) session.getAttribute("client_db");
if (clientDb != null) {
builder.setHeader("client_db", clientDb);
LOG.debug("client_db=" + clientDb);
}
}
return builder;
}
@Override
public URI getAuthorizationUri(HttpServletRequest request) {
// for a better security we should check for allowed redirects
String redirect = request.getParameter("redirect");
URI uri;
if (redirect != null) {
uri = URI.create(redirect);
} else {
uri = super.getAuthorizationUri(request);
}
String clientDb = request.getParameter(CLIENT_DB);
if (clientDb == null) {
HttpSession session = request.getSession(false);
if (session != null) {
clientDb = (String) session.getAttribute(CLIENT_DB);
}
}
if (clientDb == null) {
throw new BadRequestException("client_db not set");
}
redirect = uri.toString();
redirect += redirect.contains("?") ? "&" : "?";
redirect += CLIENT_DB + "=" + clientDb;
return URI.create(redirect);
}
@Override
public URI getAccessTokenUri(HttpServletRequest request) {
String tokenUri = request.getParameter("token_uri");
if (tokenUri != null) {
return URI.create(tokenUri);
}
return super.getAccessTokenUri(request);
}
@Override
public List<RapUser> getUsers(String searchText, HttpServletRequest request) {
List<RapUser> users = new GetUserCall(this).getUsers(searchText, request);
return users.stream()
.filter(u -> u.getDisplayName().contains(searchText) || u.getPrimaryEmailAddress().contains(searchText))
.collect(Collectors.toList());
}
}
......@@ -26,6 +26,11 @@ public class GmsLoginFilter extends LoginFilter {
private boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
if (request.getUserPrincipal() != null) {
// Principal set using JWT
return true;
}
// Allow CORS check
if ("OPTIONS".equals(request.getMethod())) {
return true;
......
package it.inaf.ia2.gms.authn;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SigningKeyResolver;
import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.gms.persistence.LoggingDAO;
import java.io.IOException;
import java.security.Principal;
......@@ -16,15 +14,16 @@ import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class JWTFilter implements Filter {
private final LoggingDAO loggingDAO;
private final SigningKeyResolver signingKeyResolver;
private final UserManager userManager;
public JWTFilter(LoggingDAO loggingDAO) {
public JWTFilter(LoggingDAO loggingDAO, UserManager userManager) {
this.loggingDAO = loggingDAO;
this.signingKeyResolver = ServiceLocator.getInstance().getTokenManager().getSigningKeyResolver();
this.userManager = userManager;
}
@Override
......@@ -34,19 +33,26 @@ public class JWTFilter implements Filter {
HttpServletResponse response = (HttpServletResponse) res;
String authHeader = request.getHeader("Authorization");
if (authHeader == null) {
loggingDAO.logAction("Attempt to access WS without token", request);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing Authorization token");
if (request.isRequestedSessionIdValid()) {
HttpSession session = request.getSession(false);
User user = (User) session.getAttribute("user_data");
if (user != null) {
ServletRequestWithSessionPrincipal wrappedRequest = new ServletRequestWithSessionPrincipal(request, user);
fc.doFilter(wrappedRequest, res);
return;
}
}
fc.doFilter(req, res);
return;
}
authHeader = authHeader.replace("Bearer", "").trim();
String token = authHeader.replace("Bearer", "").trim();
Jwt jwt = Jwts.parser()
.setSigningKeyResolver(signingKeyResolver)
.parse(authHeader);
Map<String, Object> claims = (Map<String, Object>) jwt.getBody();
Map<String, Object> claims = userManager.parseIdTokenClaims(token);
if (claims.get("sub") == null) {
loggingDAO.logAction("Attempt to access WS with invalid token", request);
......@@ -54,19 +60,34 @@ public class JWTFilter implements Filter {
return;
}
ServletRequestWithJWTPrincipal wrappedRequest = new ServletRequestWithJWTPrincipal(request, claims);
ServletRequestWithJWTPrincipal wrappedRequest = new ServletRequestWithJWTPrincipal(request, token, claims);
loggingDAO.logAction("WS access from " + wrappedRequest.getUserPrincipal().getName(), request);
fc.doFilter(wrappedRequest, res);
}
private static class ServletRequestWithSessionPrincipal extends HttpServletRequestWrapper {
private final User principal;
public ServletRequestWithSessionPrincipal(HttpServletRequest request, User user) {
super(request);
this.principal = user;
}
@Override
public Principal getUserPrincipal() {
return principal;
}
}
private static class ServletRequestWithJWTPrincipal extends HttpServletRequestWrapper {
private final Principal principal;
private final RapPrincipal principal;
public ServletRequestWithJWTPrincipal(HttpServletRequest request, Map<String, Object> jwtClaims) {
public ServletRequestWithJWTPrincipal(HttpServletRequest request, String token, Map<String, Object> jwtClaims) {
super(request);
this.principal = new RapPrincipal(jwtClaims);
this.principal = new RapPrincipal(token, jwtClaims);
}
@Override
......
package it.inaf.ia2.gms.authn;
import it.inaf.ia2.rap.client.BoundedRapClient;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
@Component
@RequestScope
public class RapClient extends BoundedRapClient<HttpServletRequest> {
@Autowired
public RapClient(ServletRapClient servletRapClient, HttpServletRequest request) {
super(servletRapClient, request);
}
}
......@@ -5,10 +5,12 @@ import java.util.Map;
public class RapPrincipal implements Principal {
private final String token;
private final String sub;
private final String altSub;
public RapPrincipal(Map<String, Object> jwtClaims) {
public RapPrincipal(String token, Map<String, Object> jwtClaims) {
this.token = token;
sub = (String) jwtClaims.get("sub");
altSub = (String) jwtClaims.get("alt_sub");
}
......@@ -24,4 +26,8 @@ public class RapPrincipal implements Principal {
public String getAlternativeName() {
return altSub;
}
public String getToken() {
return token;
}
}
package it.inaf.ia2.gms.authn;
import it.inaf.ia2.aa.AuthConfig;
import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.gms.persistence.LoggingDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
......@@ -44,9 +46,9 @@ public class SecurityConfig {
}
@Bean
public FilterRegistrationBean clientDbFilter() {
public FilterRegistrationBean clientDbFilter(AuthConfig authConfig, UserManager userManager) {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new ClientDbFilter());
bean.setFilter(new ClientDbFilter(authConfig, userManager));
bean.addUrlPatterns("/*");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
......@@ -56,10 +58,10 @@ public class SecurityConfig {
* Checks JWT for web services.
*/
@Bean
public FilterRegistrationBean serviceJWTFilter(LoggingDAO loggingDAO) {
public FilterRegistrationBean serviceJWTFilter(LoggingDAO loggingDAO, UserManager userManager) {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new JWTFilter(loggingDAO));
bean.addUrlPatterns("/ws/jwt/*");
bean.setFilter(new JWTFilter(loggingDAO, userManager));
bean.addUrlPatterns("/*");
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
......
package it.inaf.ia2.gms.authn;
import it.inaf.ia2.aa.data.User;
import it.inaf.ia2.rap.client.RapClient;
import java.security.Principal;
import javax.servlet.http.HttpServletRequest;
public class ServletRapClient extends RapClient<HttpServletRequest> {
public ServletRapClient(String baseUrl) {
super(baseUrl);
}
@Override
protected String getAccessToken(HttpServletRequest request) {
Principal principal = request.getUserPrincipal();
if (principal != null) {
if (principal instanceof User) {
return ((User) principal).getAccessToken();
}
if (principal instanceof RapPrincipal) {
return ((RapPrincipal) principal).getToken();
}
}
return null;
}
}
......@@ -14,58 +14,40 @@ public class SessionData {
private static final String USER_DATA = "user_data";
private User user;
@Autowired
private HttpServletRequest request;
private String userId;
private String userName;
private String accessToken;
private String refreshToken;
private long expiration;
@PostConstruct
public void init() {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute(USER_DATA) != null) {
User user = (User) session.getAttribute(USER_DATA);
userId = user.getName();
userName = user.getUserLabel();
accessToken = user.getAccessToken();
refreshToken = user.getRefreshToken();
setExpiresIn(user.getExpiresIn());
setUser((User) session.getAttribute(USER_DATA));
}
}
public String getUserId() {
return userId;
}
public String getAccessToken() {
return accessToken;
public void setUser(User user) {
this.user = user;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public String getRefreshToken() {
return refreshToken;
public String getUserId() {
return user.getName();
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
public String getUserName() {
return user.getUserLabel();
}
public String getUserName() {
return userName;
public String getAccessToken() {
return user.getAccessToken();
}
public void setExpiresIn(long expiresIn) {
this.expiration = System.currentTimeMillis() + expiresIn * 1000;
public String getRefreshToken() {
return user.getRefreshToken();
}
public long getExpiresIn() {
return (expiration - System.currentTimeMillis()) / 1000;
return user.getExpiresIn();
}
}
package it.inaf.ia2.gms.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.opencsv.CSVWriter;
import it.inaf.ia2.gms.authn.SessionData;
import it.inaf.ia2.gms.manager.GroupStatusManager;
import it.inaf.ia2.gms.manager.GroupsManager;
import it.inaf.ia2.gms.model.request.AddGroupRequest;
......@@ -13,9 +13,13 @@ import it.inaf.ia2.gms.model.request.GroupsRequest;
import it.inaf.ia2.gms.model.request.RenameGroupRequest;
import it.inaf.ia2.gms.model.request.SearchFilterRequest;
import it.inaf.ia2.gms.persistence.model.GroupEntity;
import it.inaf.ia2.gms.service.GroupNameService;
import it.inaf.ia2.gms.service.GroupsTreeBuilder;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -28,13 +32,16 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GroupsController {
private static final ObjectMapper MAPPER = new ObjectMapper();
@Autowired
private SessionData session;
private HttpServletRequest servletRequest;
@Autowired
private GroupsManager groupsManager;
......@@ -48,6 +55,9 @@ public class GroupsController {
@Autowired
private GroupStatusManager groupStatusManager;
@Autowired
private GroupNameService groupNameService;
@GetMapping(value = "/groups", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getGroupsTab(@Valid GroupsRequest request) {
if (request.isOnlyPanel()) {
......@@ -93,21 +103,40 @@ public class GroupsController {
return ResponseEntity.ok(groupsPanel);
}
@GetMapping(value = "/group/status/{groupId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void downloadStatus(@PathVariable("groupId") String groupId, HttpServletResponse response) throws Exception {
@GetMapping(value = "/group/status", produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE, MediaType.APPLICATION_JSON_VALUE})
public void downloadStatus(@RequestParam(value = "groupId", required = false) String groupId,
@RequestParam(value = "groupName", required = false) String groupName,
HttpServletRequest request, HttpServletResponse response) throws Exception {
if (groupId == null && groupName == null) {
response.sendError(400, "Parameter groupId or groupName is required");
return;
}
if (groupId == null) {
GroupEntity group = groupNameService.getGroupFromNames(Optional.of(groupName));
groupId = group.getId();
}
List<String[]> status = groupStatusManager.generateStatus(groupId);
try (OutputStream out = response.getOutputStream();
CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
try ( OutputStream out = response.getOutputStream()) {
writer.writeNext(new String[]{"program", "email"});
if ("application/json".equals(request.getHeader("Accept"))) {
MAPPER.writeValue(out, status);
} else {
try ( CSVWriter writer = new CSVWriter(new OutputStreamWriter(out))) {
writer.writeNext(new String[]{"program", "email"});
for (String[] row : groupStatusManager.generateStatus(groupId)) {
writer.writeNext(row);
for (String[] row : status) {
writer.writeNext(row);
}
}
}
}
}
private <T extends PaginatedModelRequest & SearchFilterRequest> PaginatedData<GroupNode> getGroupsPanel(GroupEntity parentGroup, T request) {
return groupsTreeBuilder.listSubGroups(parentGroup, request, session.getUserId());
return groupsTreeBuilder.listSubGroups(parentGroup, request, servletRequest.getUserPrincipal().getName());
}
}
package it.inaf.ia2.gms.controller;
import it.inaf.ia2.gms.authn.SessionData;
import it.inaf.ia2.gms.manager.GroupsManager;
import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
import it.inaf.ia2.gms.manager.PermissionsManager;
......@@ -10,6 +9,7 @@ import it.inaf.ia2.gms.model.response.GroupsTabResponse;
import it.inaf.ia2.gms.persistence.model.GroupEntity;
import it.inaf.ia2.gms.service.GroupsService;
import it.inaf.ia2.gms.service.GroupsTreeBuilder;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
......@@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
public class GroupsTabResponseBuilder {
@Autowired
private SessionData session;
HttpServletRequest servletRequest;
@Autowired
private PermissionsManager permissionsManager;
......@@ -46,7 +46,7 @@ public class GroupsTabResponseBuilder {
Permission permission = permissionsManager.getCurrentUserPermission(group);
response.setPermission(permission);
response.setGroupsPanel(groupsListBuilder.listSubGroups(group, request, session.getUserId()));
response.setGroupsPanel(groupsListBuilder.listSubGroups(group, request, servletRequest.getUserPrincipal().getName()));
response.setLeaf(group.isLeaf());
......
......@@ -83,6 +83,10 @@ public class HomePageController {
@GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
public String index(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// This page MUST NOT be cached to avoid losing the login redirect
response.setHeader("Cache-Control", "no-store, must-revalidate");
response.setHeader("Expires", "0");
Optional<List<InvitedRegistration>> optReg = invitedRegistrationManager.completeInvitedRegistrationIfNecessary();
if (optReg.isPresent()) {
request.setAttribute("invited-registrations", optReg.get());
......
......@@ -7,7 +7,6 @@ import it.inaf.ia2.gms.manager.InvitedRegistrationManager;
import it.inaf.ia2.gms.manager.MembershipManager;
import it.inaf.ia2.gms.manager.PermissionsManager;
import it.inaf.ia2.gms.model.Permission;
import it.inaf.ia2.gms.model.RapUser;
import it.inaf.ia2.gms.model.response.UserPermission;
import it.inaf.ia2.gms.persistence.GroupsDAO;
import it.inaf.ia2.gms.persistence.PermissionsDAO;
......@@ -19,6 +18,7 @@ import it.inaf.ia2.gms.service.GroupsService;
import it.inaf.ia2.gms.service.JoinService;
import it.inaf.ia2.gms.service.PermissionUtils;
import it.inaf.ia2.gms.service.SearchService;
import it.inaf.ia2.rap.data.RapUser;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.Principal;
......@@ -39,15 +39,16 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* Web service called by other web applications using JWT (delegation).
* This class needs some refactoring: it contains all endpoints that used JWT.
* Now all endpoints accept both a JWT token or a session, so some of them could
* be removed and others should be moved on dedicated classes. Some endpoints
* match 2 patters to achieve a smooth transition.
*/
@RestController
@RequestMapping("/ws/jwt")
public class JWTWebServiceController {
@Autowired
......@@ -63,7 +64,7 @@ public class JWTWebServiceController {
private GroupsService groupsService;
@Autowired
private GroupNameService groupNameService;
protected GroupNameService groupNameService;
@Autowired
private MembershipManager membershipManager;
......@@ -83,7 +84,7 @@ public class JWTWebServiceController {
/**
* This endpoint is compliant with the IVOA GMS standard.
*/
@GetMapping(value = "/search", produces = MediaType.TEXT_PLAIN_VALUE)
@GetMapping(value = {"/ws/jwt/search", "/vo/search"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void getGroups(HttpServletResponse response) throws IOException {
List<GroupEntity> memberships = membershipManager.getCurrentUserMemberships();
......@@ -104,10 +105,10 @@ public class JWTWebServiceController {
* be defined adding ".+", otherwise Spring will think it is a file
* extension (thanks https://stackoverflow.com/a/16333149/771431)
*/
@GetMapping(value = "/search/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
@GetMapping(value = {"/ws/jwt/search/{group:.+}", "/vo/search/{group:.+}"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void isMemberOf(@PathVariable("group") String group, HttpServletResponse response) throws IOException {
List<String> groupNames = extractGroupNames(group);
List<String> groupNames = groupNameService.extractGroupNames(group);
boolean isMember = membershipManager.isCurrentUserMemberOf("ROOT");
if (!isMember) {
......@@ -135,13 +136,12 @@ public class JWTWebServiceController {
// else: empty response (as defined by GMS standard)
}
@GetMapping(value = {"/list/{group:.+}", "/list"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void listGroups(@PathVariable("group") Optional<String> group, Principal principal, HttpServletResponse response) throws IOException {
@GetMapping(value = {"/ws/jwt/list/{group:.+}", "/ws/jwt/list"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void listGroups(@PathVariable("group") Optional<String> groupNames, Principal principal, HttpServletResponse response) throws IOException {
String userId = principal.getName();
List<String> groupNames = extractGroupNames(group);
GroupEntity parentGroup = getGroupFromNames(groupNames);
GroupEntity parentGroup = groupNameService.getGroupFromNames(groupNames);
List<GroupEntity> allSubGroups = groupsDAO.getDirectSubGroups(parentGroup.getPath());
......@@ -157,7 +157,7 @@ public class JWTWebServiceController {
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (String groupName : groupNameService.getGroupsNames(visibleSubgroups)) {
pw.println(getShortGroupName(groupName, group));
pw.println(groupNameService.getShortGroupName(groupName, groupNames));
}
}
}
......@@ -166,10 +166,10 @@ public class JWTWebServiceController {
* Creates a group and its ancestors if they are missing. It doesn't fail if
* the last group already exists.
*/
@PostMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(value = "/ws/jwt/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
public void createGroup(@PathVariable("group") String groupParam, HttpServletRequest request, HttpServletResponse response) throws IOException {
List<String> groupNames = extractGroupNames(groupParam);
List<String> groupNames = groupNameService.extractGroupNames(groupParam);
String leafParam = request.getParameter("leaf");
boolean leaf = leafParam == null ? false : Boolean.valueOf(leafParam);
......@@ -191,29 +191,29 @@ public class JWTWebServiceController {
}
}
@DeleteMapping(value = "/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
@DeleteMapping(value = "/ws/jwt/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
public void deleteGroup(@PathVariable("group") String groupParam, HttpServletResponse response) {
GroupEntity group = getGroupFromNames(extractGroupNames(groupParam));
GroupEntity group = groupNameService.getGroupFromNames(Optional.of(groupParam));
groupsDAO.deleteGroup(group);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@GetMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void getMembership(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException {
@GetMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void getMembership(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId, HttpServletResponse response) throws IOException {
GroupEntity parent = getGroupFromNames(extractGroupNames(group));
GroupEntity parent = groupNameService.getGroupFromNames(groupNames);
List<GroupEntity> groups = membershipManager.getUserGroups(parent, userId);
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (String groupName : groupNameService.getGroupsNames(groups)) {
pw.println(getShortGroupName(groupName, group));
pw.println(groupNameService.getShortGroupName(groupName, groupNames));
}
}
}
@PostMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void addMember(@PathVariable("group") Optional<String> group, HttpServletRequest request, HttpServletResponse response) throws IOException {
@PostMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void addMember(@PathVariable("group") Optional<String> groupNames, HttpServletRequest request, HttpServletResponse response) throws IOException {
String targetUserId = request.getParameter("user_id");
if (targetUserId == null) {
......@@ -221,64 +221,64 @@ public class JWTWebServiceController {
return;
}
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
membershipManager.addMember(groupEntity, targetUserId);
}
@DeleteMapping(value = {"/membership/{group:.+}", "/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void removeMember(@PathVariable("group") Optional<String> group, @RequestParam("user_id") String userId,
@DeleteMapping(value = {"/ws/jwt/membership/{group:.+}", "/ws/jwt/membership"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void removeMember(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId,
HttpServletRequest request, HttpServletResponse response) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(group));
GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
membershipManager.removeMember(groupEntity, userId);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@GetMapping(value = {"/permission/{group:.+}", "/permission"}, produces = MediaType.TEXT_PLAIN_VALUE)
@GetMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void getUserPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") Optional<String> userId, HttpServletRequest request, HttpServletResponse response) throws IOException {
GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
if (userId.isPresent()) {
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (UserPermission userPermission : searchService.getUserPermission(userId.get(), permissionsManager.getCurrentUserPermissions(getRoot()))) {
for (UserPermission userPermission : searchService.getUserPermission(groupEntity, userId.get(), permissionsManager.getCurrentUserPermissions(groupEntity))) {
String group = String.join(".", userPermission.getGroupCompleteName());
pw.println(group + " " + userPermission.getPermission());
}
}
} else {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (it.inaf.ia2.gms.model.UserPermission up : permissionsManager.getAllPermissions(groupEntity)) {
for (it.inaf.ia2.gms.model.RapUserPermission up : permissionsManager.getAllPermissions(groupEntity)) {
pw.println(up.getUser().getId() + " " + up.getPermission());
}
}
}
}
@PostMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@PostMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void addPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String targetUserId, @RequestParam("permission") Permission permission) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
permissionsManager.addPermission(groupEntity, targetUserId, permission);
}
@PutMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@PutMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public void setPermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String targetUserId, @RequestParam("permission") Permission permission) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
permissionsManager.createOrUpdatePermission(groupEntity, targetUserId, permission);
}
@DeleteMapping(value = {"/permission/{group:.+}", "/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
@DeleteMapping(value = {"/ws/jwt/permission/{group:.+}", "/ws/jwt/permission/"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void removePermission(@PathVariable("group") Optional<String> groupNames, @RequestParam("user_id") String userId,
HttpServletRequest request, HttpServletResponse response) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
GroupEntity groupEntity = groupNameService.getGroupFromNames(groupNames);
permissionsManager.removePermission(groupEntity, userId);
response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}
@GetMapping(value = "/check-invited-registration", produces = MediaType.TEXT_PLAIN_VALUE)
@GetMapping(value = {"/ws/jwt/check-invited-registration", "/check-invited-registration"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void completeInvitedRegistrationIfNecessary(Principal principal, HttpServletResponse response) throws IOException {
String userId = principal.getName();
......@@ -300,7 +300,7 @@ public class JWTWebServiceController {
}
}
@PostMapping(value = "/invited-registration", produces = MediaType.TEXT_PLAIN_VALUE)
@PostMapping(value = {"/ws/jwt/invited-registration", "/invited-registration"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void addInvitedRegistration(@RequestParam("token_hash") String tokenHash, @RequestParam("email") String email,
@RequestParam("groups") String groupNamesAndPermissionsParam, HttpServletResponse response) {
......@@ -311,7 +311,7 @@ public class JWTWebServiceController {
int lastSpaceIndex = param.lastIndexOf(" ");
String groupName = param.substring(0, lastSpaceIndex);
Permission permission = Permission.valueOf(param.substring(lastSpaceIndex + 1));
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupName));
GroupEntity groupEntity = groupNameService.getGroupFromNames(Optional.of(groupName));
groupsPermissions.put(groupEntity, permission);
}
}
......@@ -321,10 +321,10 @@ public class JWTWebServiceController {
response.setStatus(HttpServletResponse.SC_CREATED);
}
@GetMapping(value = "/email/{group:.+}", produces = MediaType.TEXT_PLAIN_VALUE)
@GetMapping(value = {"/ws/jwt/email/{group:.+}", "/email/{group:.+}"}, produces = MediaType.TEXT_PLAIN_VALUE)
public void getEmailOfMembers(@PathVariable("group") String groupNames, @RequestParam("permission") Optional<Permission> permission, HttpServletResponse response) throws IOException {
GroupEntity groupEntity = getGroupFromNames(extractGroupNames(groupNames));
GroupEntity groupEntity = groupNameService.getGroupFromNames(Optional.of(groupNames));
Set<String> selectedUserIds = null;
if (permission.isPresent()) {
......@@ -340,74 +340,13 @@ public class JWTWebServiceController {
try ( PrintWriter pw = new PrintWriter(response.getOutputStream())) {
for (RapUser member : membershipManager.getMembers(groupEntity)) {
if (selectedUserIds == null || selectedUserIds.contains(member.getId())) {
pw.println(member.getPrimaryEmail());
pw.println(member.getPrimaryEmailAddress());
}
}
}
}
private GroupEntity getGroupFromNames(List<String> groupNames) {
if (groupNames.isEmpty()) {
return getRoot();
}
return getGroupFromNamesAndIndex(groupNames, groupNames.size() - 1);
}
private GroupEntity getGroupFromNamesAndIndex(List<String> groupNames, int index) {
String parentPath = ""; // starting from ROOT
GroupEntity group = null;
for (int i = 0; i < index + 1; i++) {
String groupName = groupNames.get(i);
group = groupsDAO.findGroupByParentAndName(parentPath, groupName)
.orElseThrow(() -> new BadRequestException("Unable to find group " + groupName));
parentPath = group.getPath();
}
if (group == null) {
throw new IllegalStateException();
}
return group;
}
private GroupEntity getRoot() {
return groupsDAO.findGroupById("ROOT")
.orElseThrow(() -> new IllegalStateException("Missing root group"));
}
private List<String> extractGroupNames(Optional<String> group) {
return extractGroupNames(group.orElse(null));
}
private List<String> extractGroupNames(String groupStr) {
if (groupStr == null || groupStr.isEmpty()) {
return new ArrayList<>();
}
List<String> names = new ArrayList<>();
String currentName = "";
for (int i = 0; i < groupStr.length(); i++) {
char c = groupStr.charAt(i);
// dot is the group separator and it must be escaped if used inside
// group names
if (c == '.' && groupStr.charAt(i - 1) != '\\') {
names.add(currentName.replace("\\.", "."));
currentName = "";
} else {
currentName += c;
}
}
names.add(currentName);
return names;
}
private String getShortGroupName(String completeGroupName, Optional<String> groupPrefix) {
if (groupPrefix.isPresent()) {
return completeGroupName.substring(groupPrefix.get().length() + 1);
}
return completeGroupName;
}
@PostMapping(value = "/join", produces = MediaType.APPLICATION_JSON_VALUE)
@PostMapping(value = {"/ws/jwt/join", "/join"}, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> join(RapPrincipal principal) {
String fromUser = principal.getName();
......
package it.inaf.ia2.gms.controller;
import it.inaf.ia2.aa.ServiceLocator;
import it.inaf.ia2.aa.UserManager;
import it.inaf.ia2.gms.authn.SessionData;
import it.inaf.ia2.gms.rap.RapClient;
import java.util.HashMap;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
......@@ -19,14 +21,17 @@ public class KeepAliveController {
@Autowired
private SessionData sessionData;
@Autowired
private RapClient rapClient;
private final UserManager userManager;
public KeepAliveController() {
userManager = ServiceLocator.getInstance().getUserManager();
}
@GetMapping(value = "/keepAlive", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> keepAlive() {
public ResponseEntity<?> keepAlive(HttpServletRequest request) {
LOG.trace("Keepalive called");
if (sessionData.getExpiresIn() < 60) {
rapClient.refreshToken();
sessionData.setUser(userManager.refreshToken(request));
LOG.trace("RAP token refreshed");
}
// empty JSON object response
......
......@@ -4,12 +4,12 @@ import it.inaf.ia2.gms.manager.MembershipManager;
import it.inaf.ia2.gms.manager.PermissionsManager;
import it.inaf.ia2.gms.model.request.AddMemberRequest;
import it.inaf.ia2.gms.model.response.PaginatedData;
import it.inaf.ia2.gms.model.RapUser;
import it.inaf.ia2.gms.model.request.PaginatedModelRequest;
import it.inaf.ia2.gms.model.request.RemoveMemberRequest;
import it.inaf.ia2.gms.model.request.TabRequest;
import it.inaf.ia2.gms.persistence.model.GroupEntity;
import it.inaf.ia2.gms.service.GroupsService;
import it.inaf.ia2.rap.data.RapUser;
import java.util.Collections;
import java.util.List;
import javax.validation.Valid;
......