Skip to content

Commit

Permalink
JWT Headers security module
Browse files Browse the repository at this point in the history
  • Loading branch information
david-blasby committed Mar 25, 2024
1 parent d7d36bf commit 199c54b
Show file tree
Hide file tree
Showing 14 changed files with 2,053 additions and 0 deletions.
12 changes: 12 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
<name>GeoNetwork core</name>

<dependencies>

<dependency>
<groupId>org.geoserver.community.jwt-headers</groupId>
<artifactId>jwt-headers-util</artifactId>
<version>2.25-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>net.objecthunter</groupId>
<artifactId>exp4j</artifactId>
Expand Down Expand Up @@ -304,6 +311,11 @@
<groupId>org.geotools</groupId>
<artifactId>gt-geojson</artifactId>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* Copyright (C) 2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
* Rome - Italy. email: [email protected]
*/

package org.fao.geonet.kernel.security.jwtheaders;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;


/**
* This handles the JWT-Headers authentication filter. It's based on the Shibboleth filter.
*
*/
public class JwtHeadersAuthFilter extends GenericFilterBean {

@Autowired
public JwtHeadersUserUtil jwtHeadersUserUtil;

JwtHeadersConfiguration jwtHeadersConfiguration;


public JwtHeadersAuthFilter(JwtHeadersConfiguration jwtHeadersConfiguration) {
this.jwtHeadersConfiguration = jwtHeadersConfiguration;
}

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
var existingAuth = SecurityContextHolder.getContext().getAuthentication();
HttpServletRequest request = (HttpServletRequest) servletRequest;


var config = jwtHeadersConfiguration.getJwtConfiguration();

var user = JwtHeadersTrivialUser.create(config, request);

if (user == null && existingAuth != null) {
if (existingAuth instanceof JwtHeadersUsernamePasswordAuthenticationToken) {
//at this point, there isn't a JWT header, but there's an existing auth that was made by us (JWT header)
// in this case, we need to log-off. They have a JSESSION auth that is no longer valid.
logout(request);
filterChain.doFilter(servletRequest, servletResponse);
return;
}
}


if (user == null) {
filterChain.doFilter(servletRequest, servletResponse);
return; // no valid user in header
}

//we have a valid user in the headers

//existing user is the same user as the request
if (existingAuth != null && existingAuth.getName().equals(user.getUsername())) {
filterChain.doFilter(servletRequest, servletResponse);
return; // abort early - no need to do an expensive login. Use the existing one.
}

//existing user isnt the same user as the request
if (existingAuth != null && !existingAuth.getName().equals(user.getUsername())) {
//in this case there are two auth's - the existing one (likely from JSESSION)
//and one coming in from the JWT headers. In this case, we kill the other login
//and make a new one.
logout(request);
}

var userDetails = jwtHeadersUserUtil.getUser(user, jwtHeadersConfiguration);
if (userDetails != null) {
UsernamePasswordAuthenticationToken auth = new JwtHeadersUsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
auth.setDetails(userDetails);
SecurityContextHolder.getContext().setAuthentication(auth);

}

filterChain.doFilter(servletRequest, servletResponse);
}

/**
* handle a logout - clear out the security context, and invalidate the session
* @param request
* @throws ServletException
*/
public void logout(HttpServletRequest request) throws ServletException {
request.logout();//dont think this does anything in GN
SecurityContextHolder.getContext().setAuthentication(null);
request.getSession().invalidate();
}

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2024 Food and Agriculture Organization of the
* United Nations (FAO-UN), United Nations World Food Programme (WFP)
* and United Nations Environment Programme (UNEP)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
* Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
* Rome - Italy. email: [email protected]
*/

package org.fao.geonet.kernel.security.jwtheaders;

import org.fao.geonet.kernel.security.SecurityProviderConfiguration;
import org.geoserver.security.jwtheaders.JwtConfiguration;

/**
* configuration for the JWT Headers security filter.
* See GN documentation.
* This is based on GeoServer's JWT-Headers Module, so you can see there as well.
*
* This class handles the GN filter configuration details, and hands the actual configuration
* for the filter to the JwtConfiguration class. This class is also used in Geoserver.
*
*/
public class JwtHeadersConfiguration implements SecurityProviderConfiguration {


public LoginType loginType = LoginType.AUTOLOGIN;
/**
* true -> update the DB with the information from OIDC (don't allow user to edit profile in the UI)
* false -> don't update the DB (user must edit profile in UI).
*/
public boolean updateProfile = true;
/**
* true -> update the DB (user's group) with the information from OIDC (don't allow admin to edit user's groups in the UI)
* false -> don't update the DB (admin must edit groups in UI).
*/
public boolean updateGroup = true;
protected JwtConfiguration jwtConfiguration;


// getters/setters


public JwtHeadersConfiguration() {
jwtConfiguration = new JwtConfiguration();
}

public boolean isUpdateProfile() {
return updateProfile;
}

public void setUpdateProfile(boolean updateProfile) {
this.updateProfile = updateProfile;
}

public boolean isUpdateGroup() {
return updateGroup;
}


//---- abstract class methods

public void setUpdateGroup(boolean updateGroup) {
this.updateGroup = updateGroup;
}

@Override
public String getLoginType() {
return loginType.toString();
}

@Override
public String getSecurityProvider() {
return "JWT-HEADERS";
}

@Override
public boolean isUserProfileUpdateEnabled() {
// If updating profile from the security provider then disable the profile updates in the interface
return !updateProfile;
}

//========================================================================

@Override
public boolean isUserGroupUpdateEnabled() {
// If updating group from the security provider then disable the group updates in the interface
return !updateGroup;
}

public org.geoserver.security.jwtheaders.JwtConfiguration getJwtConfiguration() {
return jwtConfiguration;
}

public void setJwtConfiguration(
org.geoserver.security.jwtheaders.JwtConfiguration jwtConfiguration) {
this.jwtConfiguration = jwtConfiguration;
}

}
Loading

0 comments on commit 199c54b

Please sign in to comment.