diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fefd34..baaac27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Removed + +* Removed support for authentication via the now deprecated OpenID 1.0 / OpenID 2.0 protocols. ## [4.1.3] - 2026-04-28 @@ -20,3 +23,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Update transitive dependency on org.bouncycastle:bcprov-jdk18on to 1.84 to address dependabot-reported vulnerabilities. +## [4.0.0] - 2025-12-16 + +* Move from Java 11 to 21 +* Update appbase dependency to 4.0.0 +* Update derby 10.14 -> 10.17 +* Move from javax.servlet -> jakarta.servlet 6.0 + +## [3.0.3] - 2025-07-24 + +* Update shiro to 1.13.0 to avoid most severe CVEs. Move to Shiro2.x would be breaking and there's no migration documentation. +* Update appbase dependency to pull in update to tomcat 9.0 + diff --git a/README.md b/README.md index e65d16b..98af72b 100644 --- a/README.md +++ b/README.md @@ -6,19 +6,10 @@ appbase-security * Provides a `UserStore` for holding registered users and associated permissions, optionally including password credentials. Includes a database-backed store based on embedded [Derby](https://db.apache.org/derby/) and a memory based implementation which is loaded from a configuration file. * Provides a Realm implementation which has an associated `UserStore` and allows authentication tokens which are externally validated. * Permissions structure is based on Shiro Wildcard permissions but assumes a simplified pattern of `"{action}:{location}"`. Permissions can be retrieved by location as well as by user. - * Provides packaged access to an [OpenID](http://openid.net/) implementation so it's easy to create applications where you can register and login using OpenID such as Google. ## Change log: -**4.0.0** - * Move from Java 11 to 21 - * Update appbase dependency to 4.0.0 - * Update derby 10.14 -> 10.17 - * Move from javax.servlet -> jakarta.servlet 6.0 - -**3.0.3** - * Update shiro to 1.13.0 to avoid most severe CVEs. Move to Shiro2.x would be breaking and there's no migration documentation. - * Update appbase dependency to pull in update to tomcat 9.0 +See the separate [CHANGELOG.md](CHANGELOG.md). ## Usage @@ -157,18 +148,10 @@ user id "name" password For example: ```INI -user https://profiles.google.com/1147194443288764760228 "Alice" user dave@epimorphics.com "Dave Reynolds" shouldbechanged ``` -An OpenID profile for anyone with a Google account can be obtained from their profile or -Google-plus home page and copying the long number from there into the above URL pattern. -A general Google login generates an OpenID which depends on the requesting web site -as well as the user. To determine the ID in that case start the bootstrap registry, -register the target user and note the resulting OpenID, then shutdown the registry -and modify the initialization file accordingly. - -There is a built-in anonymous user with pseudo OpenID of `http://localhost/anon`. +There is a built-in anonymous user with pseudo id of `http://localhost/anon`. It is convenient to declare that user in the initialization file as well, so as to bind a visible name to that id. For example: @@ -267,111 +250,6 @@ public class SecurityViolationMapper implements } ``` -## OpenID, login and registration - -The `Login` class provides a set of convenience methods to enable user registration -and login via OpenID, login via password credentials and logout. - -To use this you need to provide a set of URL endpoints which invoke the various actions -and handle OpenID response processing. The easy way to do this is via -[Jersey](https://jersey.java.net/index.html). For example: - -```java -@Path("/system/security") -public class LoginCmds { - protected @Context UriInfo uriInfo; - protected @Context ServletContext context; - - // request OpenID login for a registered user - @Path("/login") - @POST - public Response login( - @FormParam("provider") String provider, - @FormParam("return") String returnURL, - @Context HttpServletRequest request, - @Context HttpServletResponse response) { - OpenidRequest oid = new OpenidRequest(uriInfo.getBaseUri().toString() + - "system/security/response"); - oid.setProvider(provider); - oid.setReturnURL(returnURL); - try { - processOpenID(request, response, oid); - } catch (Exception e) { - throw new WebApiException(Status.BAD_REQUEST, - "Login/registration action failed: " + e); - } - return Response.ok().build(); - } - - // Register a new user via OpenID - @Path("/register") - @POST - public Response register( - @FormParam("provider") String provider, - @FormParam("return") String returnURL, - @Context HttpServletRequest request, - @Context HttpServletResponse response) { - OpenidRequest oid = new OpenidRequest(uriInfo.getBaseUri().toString() + - "system/security/response"); - oid.setProvider(provider); - oid.setReturnURL(returnURL); - oid.setRegister(true); - try { - processOpenID(request, response, oid); - } catch (Exception e) { - throw new WebApiException(Status.BAD_REQUEST, - "Login/registration action failed: " + e); - } - return Response.ok().build(); - } - - // Logout the current loged in user - @Path("/logout") - @POST - public void doLogout(@Context HttpServletRequest request, - @Context HttpServletResponse response) throws IOException { - logout(request); - response.sendRedirect(request.getServletContext().getContextPath()); - } - - // Internal endpoint use in the OpenID handshake - @Path("/response") - @GET - public Response openIDResponse(@Context HttpServletRequest request, - @Context HttpServletResponse response) { - try { - UserStore userstore = AppConfig.getApp() - .getComponentAs("userstore", UserStore.class); - return redirectTo( verifyResponse(request, response, userstore) ); - } catch (Exception e) { - return renderError( e.getMessage() ); - } - } - - private Response redirectTo(String path) { - URI uri; - try { - uri = new URI(path); - return Response.seeOther(uri).build(); - } catch (URISyntaxException e) { - throw new EpiException(e); - } - } - - // Some means to report login errors, this assumes AppBase velocity rendering using a generic error.vm template - private Response renderError(String message) { - VelocityRender velocity = AppConfig.getApp() - .getComponentAs("velocity", VelocityRender.class); - StreamingOutput out = velocity.render("error.vm", - uriInfo.getPath(), - context, - uriInfo.getQueryParameters(), - "message", message); - return Response.status(Status.BAD_REQUEST).entity(out).build(); - } -} -``` - ## Other The `UserStore` implementation also provides various methods for accessing available diff --git a/pom.xml b/pom.xml index afd667e..5c36c7d 100644 --- a/pom.xml +++ b/pom.xml @@ -107,40 +107,6 @@ 3.20.0 - - org.openid4java - openid4java - 1.0.0 - - - org.apache.httpcomponents - httpclient - - - org.apache.httpcomponents - httpcore - - - xerces - xercesImpl - - - net.sourceforge.nekohtml - nekohtml - - - - - xerces - xercesImpl - 2.12.2 - - - net.sourceforge.nekohtml - nekohtml - 1.9.22 - - org.apache.shiro shiro-core diff --git a/src/main/java/com/epimorphics/appbase/security/Login.java b/src/main/java/com/epimorphics/appbase/security/Login.java index 5cfbe48..b0c9575 100644 --- a/src/main/java/com/epimorphics/appbase/security/Login.java +++ b/src/main/java/com/epimorphics/appbase/security/Login.java @@ -32,7 +32,7 @@ import com.epimorphics.util.EpiException; /** - * Utility functions for registration and login via OpenID. + * Utility functions for registration and login. * Binding these to resource URIs via jersey in the web application. * * @author Dave Reynolds diff --git a/src/main/java/com/epimorphics/appbase/security/ProcessOpenID.java b/src/main/java/com/epimorphics/appbase/security/ProcessOpenID.java deleted file mode 100644 index d50319b..0000000 --- a/src/main/java/com/epimorphics/appbase/security/ProcessOpenID.java +++ /dev/null @@ -1,274 +0,0 @@ -/****************************************************************** - * File: ProcessOpenID.java - * Created by: Dave Reynolds - * Created on: 15 Jul 2014 - * - * (c) Copyright 2014, Epimorphics Limited - * - *****************************************************************/ - -package com.epimorphics.appbase.security; - -import java.util.List; -import java.util.Map; - -import jakarta.servlet.http.Cookie; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; - -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; -import org.openid4java.consumer.ConsumerManager; -import org.openid4java.consumer.VerificationResult; -import org.openid4java.discovery.DiscoveryInformation; -import org.openid4java.discovery.Identifier; -import org.openid4java.message.AuthRequest; -import org.openid4java.message.AuthSuccess; -import org.openid4java.message.ParameterList; -import org.openid4java.message.ax.AxMessage; -import org.openid4java.message.ax.FetchRequest; -import org.openid4java.message.ax.FetchResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.epimorphics.util.EpiException; - -public class ProcessOpenID { - static final Logger log = LoggerFactory.getLogger( ProcessOpenID.class ); - - public static final String DEFAULT_PROVIDER = "https://www.google.com/accounts/o8/id"; - public static final String PROVIDER_COOKIE = "appbase-login-provider"; - - // Session attribute names - public static final String SA_OPENID_DISC = "openid_disc"; - public static final String SA_OPENID_PROVIDER = "openid_provider"; - public static final String SA_REGISTRATION = "isRegistration"; - public static final String SA_RETURN_URL = "returnURL"; - - // Attribute parameter names - public static final String AP_EMAIL = "email"; - public static final String AP_FIRST_NAME = "firstName"; - public static final String AP_LAST_NAME = "lastName"; - public static final String AP_FULL_NAME = "fullname"; - - // Velocity binding names - public static final String VN_REGISTRATION_STATUS = "registrationStatus"; - public static final String RS_NEW = "new"; - public static final String RS_ALREADY_REGISTERED = "already"; - public static final String RS_LOGIN = "login"; - - private static ConsumerManager manager = null; - static { - try { - manager = new ConsumerManager(); - } catch (Exception e) { - log.error("Failed to initialize openid subsystem", e); - } - } - - /** - * Perform a login or registration via OpenID. - * @throws EpiException if the request is malformed in some way. - */ - @SuppressWarnings("rawtypes") - static public void processOpenID(HttpServletRequest request, HttpServletResponse response, OpenidRequest oid) { - HttpSession session = request.getSession(); - session.setAttribute(SA_REGISTRATION, oid.isRegister()); - session.setAttribute(SA_OPENID_PROVIDER, oid.getProvider()); - session.setAttribute(SA_RETURN_URL, oid.getReturnURL()); - - log.info("Authentication request for " + oid.getProvider() + (oid.isRegister() ? " (registration)" : "")); - - try - { - // perform discovery on the user-supplied identifier - List discoveries = manager.discover(oid.getProvider()); - - // attempt to associate with the OpenID provider - // and retrieve one service endpoint for authentication - DiscoveryInformation discovered = manager.associate(discoveries); - - // store the discovery information in the user's session - request.getSession().setAttribute(SA_OPENID_DISC, discovered); - - // obtain a AuthRequest message to be sent to the OpenID provider - AuthRequest authReq = manager.authenticate(discovered, oid.getResponseURL()); - - if (oid.isRegister()) { - // Attribute Exchange example: fetching the 'email' attribute - FetchRequest fetch = FetchRequest.createFetchRequest(); - if (oid.getProvider().contains("google.com")) { -// fetch.addAttribute(AP_EMAIL, "http://axschema.org/contact/email", false); - fetch.addAttribute(AP_FIRST_NAME, "http://axschema.org/namePerson/first", true); - fetch.addAttribute(AP_LAST_NAME, "http://axschema.org/namePerson/last", true); - } else if (oid.getProvider().contains("yahoo.com")) { -// fetch.addAttribute(AP_EMAIL, "http://axschema.org/contact/email", false); - fetch.addAttribute(AP_FULL_NAME, "http://axschema.org/namePerson", true); - } else { //works for myOpenID -// fetch.addAttribute(AP_EMAIL, "http://schema.openid.net/contact/email", false); - fetch.addAttribute(AP_FULL_NAME, "http://schema.openid.net/namePerson", true); - } - - // attach the extension to the authentication request - authReq.addExtension(fetch); - } - - // For version2 endpoints can do a form-redirect but this is easier, - // Relies on payload being less ~ 2k, currently ~ 800 bytes - response.sendRedirect(authReq.getDestinationUrl(true)); - } - catch (Exception e) - { - throw new EpiException("Login/registration action failed: " + e); - } - } - - /** - * Process the verification response from the OpenID provider. This should be called - * from a URL which is given as part of the original OpenIDRequest. If the verification - * was successful it returns the URL to which the user should be redirected (specified - * in the original call), otherwise an EpiExpception is thrown. - */ - @SuppressWarnings({ "unchecked" }) - static public String verifyResponse(HttpServletRequest request, HttpServletResponse httpresponse, UserStore userstore) { - try { - HttpSession session = request.getSession(); - - // extract the parameters from the authentication response - // (which comes in as a HTTP request from the OpenID provider) - ParameterList response = - new ParameterList(request.getParameterMap()); - - // retrieve the previously stored discovery information - DiscoveryInformation discovered = (DiscoveryInformation) - session.getAttribute("openid-disc"); - - // extract the receiving URL from the HTTP request - StringBuffer receivingURL = request.getRequestURL(); - String queryString = request.getQueryString(); - if (queryString != null && queryString.length() > 0) - receivingURL.append("?").append(request.getQueryString()); - - // verify the response; ConsumerManager needs to be the same - // (static) instance used to place the authentication request - VerificationResult verification = manager.verify( - receivingURL.toString(), - response, discovered); - - // examine the verification result and extract the verified identifier - Identifier verified = verification.getVerifiedId(); - if (verified != null) { - AuthSuccess authSuccess = (AuthSuccess) verification.getAuthResponse(); - String name = null; - if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) { - FetchResponse fetchResp = (FetchResponse) authSuccess - .getExtension(AxMessage.OPENID_NS_AX); - Map> attributes = fetchResp.getAttributes(); - if (attributes.containsKey(AP_FULL_NAME)) { - name = attributes.get(AP_FULL_NAME).get(0); - } else { - name = attributes.get(AP_FIRST_NAME).get(0) + " " + attributes.get(AP_LAST_NAME).get(0); - } - } - log.info(String.format("Verified identity %s = %s", verified.getIdentifier(), name)); - boolean isRegistration = ((Boolean)session.getAttribute(SA_REGISTRATION)).booleanValue(); - String registrationStatus = RS_LOGIN; - if (isRegistration) { - UserInfo userinfo = new UserInfo(verified.getIdentifier(), name); - if (userstore.register( userinfo )) { - registrationStatus = RS_NEW; - } else { - registrationStatus = RS_ALREADY_REGISTERED; - } - } - - AppRealmToken token = new AppRealmToken(verified.getIdentifier(), true); - Subject subject = SecurityUtils.getSubject(); - try { - subject.login(token); - session.setAttribute(VN_REGISTRATION_STATUS, registrationStatus); - String provider = (String)session.getAttribute(SA_OPENID_PROVIDER); - if (provider != null && !provider.isEmpty()) { - Cookie cookie = new Cookie(PROVIDER_COOKIE, provider); - cookie.setMaxAge(60 * 60 * 24 * 30); - cookie.setHttpOnly(true); - cookie.setPath("/"); - httpresponse.addCookie(cookie); - } - return session.getAttribute(SA_RETURN_URL).toString(); - } catch (Exception e) { - log.error("Authentication failure", e); - throw new EpiException("Could not find a registration."); - } - } - } catch (Exception e) { - throw new EpiException(e); - } - throw new EpiException("OpenID login failed"); - } - - - /** - * Packaged set of parameters for an OpenID login or registration request. - * - * @author Dave Reynolds - */ - static public class OpenidRequest { - String provider = DEFAULT_PROVIDER; - String responseURL; - String returnURL = "/"; - boolean isRegister = false; - - /** - * Create a login or registration request - * @param responseURL The URL to use for the OpenID response, this endpoint should invoke a verifyRequest call - */ - public OpenidRequest(String responseURL) { - this.responseURL = responseURL; - } - - /** - * Set the OpenID provider to use. The default is generic Google login (which is - * distinct from a person-specific Google profile provider) - */ - public void setProvider(String provider) { - if (provider == null) { - this.provider = DEFAULT_PROVIDER; - } else { - this.provider = provider; - } - } - - /** - * Set the URL to which the user will be redirected after a successful login - */ - public void setReturnURL(String returnURL) { - this.returnURL = returnURL; - } - - /** - * Set to true if this is a registration rather than a login (default is login) - */ - public void setRegister(boolean isRegister) { - this.isRegister = isRegister; - } - - public String getProvider() { - return provider; - } - - public String getResponseURL() { - return responseURL; - } - - public String getReturnURL() { - return returnURL; - } - - public boolean isRegister() { - return isRegister; - } - - } -}