From a71472d9094032312cef4fe197fb84269450a7a9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 23 Apr 2013 07:16:28 +0200 Subject: [PATCH 1/4] start implementation of a remember me system --- .../main/java/sonia/scm/security/Tokens.java | 25 +++++++++++++++++-- .../web/filter/BasicAuthenticationFilter.java | 2 +- .../resources/AuthenticationResource.java | 16 +++++++++--- .../src/main/webapp/resources/js/i18n/de.js | 3 ++- .../resources/js/login/sonia.login.form.js | 8 +++++- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/security/Tokens.java b/scm-core/src/main/java/sonia/scm/security/Tokens.java index 8d15016b56..97a9f29535 100644 --- a/scm-core/src/main/java/sonia/scm/security/Tokens.java +++ b/scm-core/src/main/java/sonia/scm/security/Tokens.java @@ -69,12 +69,33 @@ public final class Tokens * @param username username of the user to authenticate * @param password password of the user to authenticate * - * @return + * @return authentication token */ public static AuthenticationToken createAuthenticationToken( HttpServletRequest request, String username, String password) { - return new UsernamePasswordToken(username, password, + return createAuthenticationToken(request, username, password, false); + } + + /** + * Build an {@link AuthenticationToken} for use with + * {@link Subject#login(org.apache.shiro.authc.AuthenticationToken)}. + * + * + * @param request servlet request + * @param username username of the user to authenticate + * @param password password of the user to authenticate + * @param rememberMe true to remember the user across sessions + * + * @return authentication token + * + * @since 1.31 + */ + public static AuthenticationToken createAuthenticationToken( + HttpServletRequest request, String username, String password, + boolean rememberMe) + { + return new UsernamePasswordToken(username, password, rememberMe, request.getRemoteAddr()); } } diff --git a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java index fc2cc6d681..7db4eaeec6 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/BasicAuthenticationFilter.java @@ -156,7 +156,7 @@ public class BasicAuthenticationFilter extends HttpFilter } } } - else if (subject.isAuthenticated()) + else if (subject.isAuthenticated() || subject.isRemembered()) { if (logger.isTraceEnabled()) { diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java index 150d352219..c4e9618329 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/AuthenticationResource.java @@ -68,6 +68,7 @@ import java.util.Collections; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -131,6 +132,7 @@ public class AuthenticationResource * @param response the current http response * @param username the username for the authentication * @param password the password for the authentication + * @param rememberMe true to remember the user across sessions * * @return */ @@ -139,7 +141,8 @@ public class AuthenticationResource @TypeHint(ScmState.class) public ScmState authenticate(@Context HttpServletRequest request, @FormParam("username") String username, - @FormParam("password") String password) + @FormParam("password") String password, @FormParam("rememberMe") + @DefaultValue("false") boolean rememberMe) { ScmState state = null; @@ -148,7 +151,7 @@ public class AuthenticationResource try { subject.login(Tokens.createAuthenticationToken(request, username, - password)); + password, rememberMe)); state = createState(subject); } catch (AuthenticationException ex) @@ -253,11 +256,16 @@ public class AuthenticationResource Response response = null; Subject subject = SecurityUtils.getSubject(); - if (subject.isAuthenticated()) + if (subject.isAuthenticated() || subject.isRemembered()) { if (logger.isDebugEnabled()) { - logger.debug("return state for user {}", subject.getPrincipal()); + String auth = subject.isRemembered() + ? "remembered" + : "authenticated"; + + logger.debug("return state for {} user {}", auth, + subject.getPrincipal()); } ScmState state = createState(subject); diff --git a/scm-webapp/src/main/webapp/resources/js/i18n/de.js b/scm-webapp/src/main/webapp/resources/js/i18n/de.js index 27cba73886..5c595ab914 100644 --- a/scm-webapp/src/main/webapp/resources/js/i18n/de.js +++ b/scm-webapp/src/main/webapp/resources/js/i18n/de.js @@ -88,7 +88,8 @@ if (Sonia.login.Form){ WaitMsgText: 'Übertrage Daten...', failedMsgText: 'Anmeldung fehlgeschlagen!', failedDescriptionText: 'Falscher Benutzername, Passwort oder sie haben nicht\n\ - genug Berechtigungen. Bitte versuchen sie es erneut.' + genug Berechtigungen. Bitte versuchen sie es erneut.', + rememberMeText: 'Angemeldet bleiben' }); } diff --git a/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js b/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js index 22d037d6a2..aec8decb96 100644 --- a/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js +++ b/scm-webapp/src/main/webapp/resources/js/login/sonia.login.form.js @@ -39,11 +39,12 @@ Sonia.login.Form = Ext.extend(Ext.FormPanel,{ WaitMsgText: 'Sending data...', failedMsgText: 'Login failed!', failedDescriptionText: 'Incorrect username, password or not enough permission. Please Try again.', + rememberMeText: 'Remember me', initComponent: function(){ var config = { - labelWidth: 80, + labelWidth: 120, url: restUrl + "authentication/login.json", frame: true, title: this.titleText, @@ -76,6 +77,11 @@ Sonia.login.Form = Ext.extend(Ext.FormPanel,{ scope: this } } + },{ + xtype: 'checkbox', + fieldLabel: this.rememberMeText, + name: 'rememberMe', + inputValue: 'true' }], buttons:[{ text: this.cancelText, From 2e7e4c457af7d557fb9680d5cad43c4422bce225 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Apr 2013 08:33:32 +0200 Subject: [PATCH 2/4] improve remember me --- .../src/main/java/sonia/scm/repository/PermissionUtil.java | 2 +- scm-core/src/main/java/sonia/scm/util/SecurityUtil.java | 2 +- .../main/java/sonia/scm/web/filter/PermissionFilter.java | 3 ++- .../main/java/sonia/scm/plugin/rest/SubjectWrapper.java | 4 ++-- .../src/main/java/sample/hello/HelloResource.java | 2 +- .../scm/api/rest/resources/ChangePasswordResource.java | 3 ++- .../src/main/java/sonia/scm/filter/SecurityFilter.java | 7 +++---- .../src/main/java/sonia/scm/search/SearchHandler.java | 3 ++- .../src/main/java/sonia/scm/user/DefaultUserManager.java | 2 +- .../java/sonia/scm/web/security/BasicSecurityContext.java | 2 +- .../scm/web/security/DefaultAdministrationContext.java | 2 +- 11 files changed, 17 insertions(+), 15 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java index afd1d1e681..57ecda6da2 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java @@ -183,7 +183,7 @@ public final class PermissionUtil Subject subject = SecurityUtils.getSubject(); - if (subject.isAuthenticated()) + if (subject.isAuthenticated() || subject.isRemembered()) { String username = subject.getPrincipal().toString(); diff --git a/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java b/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java index dd41afd9d5..a92ba230a5 100644 --- a/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/SecurityUtil.java @@ -87,7 +87,7 @@ public final class SecurityUtil { Subject subject = SecurityUtils.getSubject(); - if (!subject.isAuthenticated()) + if (!subject.hasRole(Role.USER)) { throw new ScmSecurityException("user is not authenticated"); } diff --git a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java index 3bc8312b50..da3a8ead39 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/PermissionFilter.java @@ -65,6 +65,7 @@ import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import sonia.scm.security.Role; /** * Abstract http filter to check repository permissions. @@ -255,7 +256,7 @@ public abstract class PermissionFilter extends HttpFilter private void sendAccessDenied(HttpServletResponse response, Subject subject) throws IOException { - if (subject.isAuthenticated()) + if (subject.hasRole(Role.USER)) { response.sendError(HttpServletResponse.SC_FORBIDDEN); } diff --git a/scm-plugin-backend/src/main/java/sonia/scm/plugin/rest/SubjectWrapper.java b/scm-plugin-backend/src/main/java/sonia/scm/plugin/rest/SubjectWrapper.java index c245ecab76..2b04262ec0 100644 --- a/scm-plugin-backend/src/main/java/sonia/scm/plugin/rest/SubjectWrapper.java +++ b/scm-plugin-backend/src/main/java/sonia/scm/plugin/rest/SubjectWrapper.java @@ -73,7 +73,7 @@ public class SubjectWrapper { String name; - if (subject.isAuthenticated()) + if (subject.isAuthenticated() || subject.isRemembered()) { name = (String) subject.getPrincipal(); } @@ -104,7 +104,7 @@ public class SubjectWrapper */ public boolean isAuthenticated() { - return subject.isAuthenticated(); + return subject.isAuthenticated() || subject.isRemembered(); } //~--- fields --------------------------------------------------------------- diff --git a/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java b/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java index 2064c7ffc0..586c03ced3 100644 --- a/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java +++ b/scm-samples/scm-sample-hello/src/main/java/sample/hello/HelloResource.java @@ -66,7 +66,7 @@ public class HelloResource Subject subject = SecurityUtils.getSubject(); String displayName = "Unknown"; - if (subject.isAuthenticated()) + if (subject.isAuthenticated() || subject.isRemembered()) { displayName = subject.getPrincipals().oneByType(User.class).getDisplayName(); diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java index 48c63b3b0a..121729f65b 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java @@ -65,6 +65,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import sonia.scm.security.Role; /** * @@ -137,7 +138,7 @@ public class ChangePasswordResource Response response = null; Subject subject = SecurityUtils.getSubject(); - if (!subject.isAuthenticated()) + if (!subject.hasRole(Role.USER)) { throw new ScmSecurityException("user is not authenticated"); } diff --git a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java index ea3deec0e0..b469a6b89a 100644 --- a/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java +++ b/scm-webapp/src/main/java/sonia/scm/filter/SecurityFilter.java @@ -111,7 +111,7 @@ public class SecurityFilter extends HttpFilter chain.doFilter(new SecurityHttpServletRequestWrapper(request, getUser(subject)), response); } - else if (subject.isAuthenticated()) + else if (subject.isAuthenticated() || subject.isRemembered()) { response.sendError(HttpServletResponse.SC_FORBIDDEN); } @@ -142,8 +142,7 @@ public class SecurityFilter extends HttpFilter */ protected boolean hasPermission(Subject subject) { - return ((configuration != null) - && configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated(); + return ((configuration != null) && configuration.isAnonymousAccessEnabled()) || subject.isAuthenticated() || subject.isRemembered(); } /** @@ -158,7 +157,7 @@ public class SecurityFilter extends HttpFilter { User user = null; - if (subject.isAuthenticated()) + if (subject.isAuthenticated() || subject.isRemembered()) { user = subject.getPrincipals().oneByType(User.class); } diff --git a/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java b/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java index e3a0f25c5c..1f00328246 100644 --- a/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java +++ b/scm-webapp/src/main/java/sonia/scm/search/SearchHandler.java @@ -55,6 +55,7 @@ import java.util.Collection; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response.Status; +import sonia.scm.security.Role; /** * @@ -112,7 +113,7 @@ public class SearchHandler { Subject subject = SecurityUtils.getSubject(); - if (!subject.isAuthenticated()) + if (!subject.hasRole(Role.USER)) { throw new ScmSecurityException("Authentication is required"); } diff --git a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java index 465a66465e..3b82269bde 100644 --- a/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java +++ b/scm-webapp/src/main/java/sonia/scm/user/DefaultUserManager.java @@ -169,7 +169,7 @@ public class DefaultUserManager extends AbstractUserManager Subject subject = SecurityUtils.getSubject(); - if (!subject.isAuthenticated()) + if (!subject.hasRole(Role.USER)) { throw new ScmSecurityException("user is not authenticated"); } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java index 1cd5d645cd..ecc197e8a5 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/BasicSecurityContext.java @@ -227,7 +227,7 @@ public class BasicSecurityContext implements WebSecurityContext T result = null; Subject subject = SecurityUtils.getSubject(); - if (subject.isAuthenticated()) + if (subject.isAuthenticated() || subject.isRemembered()) { PrincipalCollection pc = subject.getPrincipals(); diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java index 34517fab79..64854f0df3 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/DefaultAdministrationContext.java @@ -242,7 +242,7 @@ public class DefaultAdministrationContext implements AdministrationContext { String username = null; - if (subject.isAuthenticated()) + if (subject.hasRole(Role.USER)) { username = principal; } From d86fe78779c40207ecd08f489a19959cddd383fe Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 24 Apr 2013 08:40:04 +0200 Subject: [PATCH 3/4] fix missing user role in mocked admin subject --- scm-test/src/main/java/sonia/scm/util/MockUtil.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scm-test/src/main/java/sonia/scm/util/MockUtil.java b/scm-test/src/main/java/sonia/scm/util/MockUtil.java index a53c9feaba..3aa41f4523 100644 --- a/scm-test/src/main/java/sonia/scm/util/MockUtil.java +++ b/scm-test/src/main/java/sonia/scm/util/MockUtil.java @@ -62,6 +62,7 @@ import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import sonia.scm.security.Role; /** * @@ -116,7 +117,8 @@ public final class MockUtil when(subject.isPermittedAll(anyCollectionOf(Permission.class))).thenReturn( Boolean.TRUE); when(subject.isPermittedAll()).thenReturn(Boolean.TRUE); - when(subject.hasRole("admin")).thenReturn(Boolean.TRUE); + when(subject.hasRole(Role.ADMIN)).thenReturn(Boolean.TRUE); + when(subject.hasRole(Role.USER)).thenReturn(Boolean.TRUE); PrincipalCollection collection = mock(PrincipalCollection.class); From c9dfd62db1c14a8fc66e5e2c5026a8e542a41776 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 26 Apr 2013 14:46:11 +0200 Subject: [PATCH 4/4] close branch issue-384