From 4a490499f9edca82be25100d9bb40c94b3a86694 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Jan 2011 11:57:15 +0100 Subject: [PATCH 1/8] added AuthenticationListener --- .../AbstractAuthenticationManager.java | 115 ++++++++++++++++++ .../web/security/AuthenticationListener.java | 64 ++++++++++ .../web/security/AuthenticationManager.java | 4 +- .../sonia/scm/BindingExtensionProcessor.java | 29 +++++ .../java/sonia/scm/ScmContextListener.java | 7 +- .../security/ChainAuthenticatonManager.java | 5 +- 6 files changed, 221 insertions(+), 3 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/web/security/AbstractAuthenticationManager.java create mode 100644 scm-core/src/main/java/sonia/scm/web/security/AuthenticationListener.java diff --git a/scm-core/src/main/java/sonia/scm/web/security/AbstractAuthenticationManager.java b/scm-core/src/main/java/sonia/scm/web/security/AbstractAuthenticationManager.java new file mode 100644 index 0000000000..080d36c62e --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/security/AbstractAuthenticationManager.java @@ -0,0 +1,115 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.user.User; + +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +public abstract class AbstractAuthenticationManager + implements AuthenticationManager +{ + + /** + * Method description + * + * + * @param listener + */ + @Override + public void addListener(AuthenticationListener listener) + { + listeners.add(listener); + } + + /** + * Method description + * + * + * @param listeners + */ + @Override + public void addListeners(Collection listeners) + { + listeners.addAll(listeners); + } + + /** + * Method description + * + * + * @param listener + */ + @Override + public void removeListener(AuthenticationListener listener) + { + listeners.remove(listener); + } + + /** + * Method description + * + * + * @param request + * @param response + * @param user + */ + protected void fireAuthenticationEvent(HttpServletRequest request, + HttpServletResponse response, User user) + { + for (AuthenticationListener listener : listeners) + { + listener.onAuthentication(request, response, user); + } + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private Set listeners = + new HashSet(); +} diff --git a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationListener.java b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationListener.java new file mode 100644 index 0000000000..301ac48a8d --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationListener.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.web.security; + +//~--- non-JDK imports -------------------------------------------------------- + +import sonia.scm.plugin.ExtensionPoint; +import sonia.scm.user.User; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * + * @author Sebastian Sdorra + */ +@ExtensionPoint +public interface AuthenticationListener +{ + + /** + * Method description + * + * + * @param request + * @param response + * @param user + */ + public void onAuthentication(HttpServletRequest request, + HttpServletResponse response, User user); +} diff --git a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java index 566d227358..d0abd7b69a 100644 --- a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java +++ b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java @@ -36,6 +36,7 @@ package sonia.scm.web.security; //~--- non-JDK imports -------------------------------------------------------- import sonia.scm.Initable; +import sonia.scm.ListenerSupport; import sonia.scm.user.User; //~--- JDK imports ------------------------------------------------------------ @@ -49,7 +50,8 @@ import javax.servlet.http.HttpServletResponse; * * @author Sebastian Sdorra */ -public interface AuthenticationManager extends Initable, Closeable +public interface AuthenticationManager + extends Initable, Closeable, ListenerSupport { /** diff --git a/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java b/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java index 174565abbc..05764efb2b 100644 --- a/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java +++ b/scm-webapp/src/main/java/sonia/scm/BindingExtensionProcessor.java @@ -50,6 +50,7 @@ import sonia.scm.repository.RepositoryListener; import sonia.scm.security.EncryptionHandler; import sonia.scm.user.UserListener; import sonia.scm.web.security.AuthenticationHandler; +import sonia.scm.web.security.AuthenticationListener; import sonia.scm.web.security.XmlAuthenticationHandler; //~--- JDK imports ------------------------------------------------------------ @@ -162,6 +163,19 @@ public class BindingExtensionProcessor implements ExtensionProcessor repositoryListeners.add(listener); } + else if (AuthenticationListener.class.isAssignableFrom(extensionClass)) + { + if (logger.isInfoEnabled()) + { + logger.info("bind AuthenticaitonListener {}", + extensionClass.getName()); + } + + AuthenticationListener listener = + (AuthenticationListener) extensionClass.newInstance(); + + authenticationListeners.add(listener); + } else { if (logger.isInfoEnabled()) @@ -206,6 +220,17 @@ public class BindingExtensionProcessor implements ExtensionProcessor //~--- get methods ---------------------------------------------------------- + /** + * Method description + * + * + * @return + */ + public Set getAuthenticationListeners() + { + return authenticationListeners; + } + /** * Method description * @@ -307,6 +332,10 @@ public class BindingExtensionProcessor implements ExtensionProcessor private Set repositoryListeners = new HashSet(); + /** Field description */ + private Set authenticationListeners = + new HashSet(); + /** Field description */ private Set userListeners = new HashSet(); diff --git a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java index a31dc4c244..11a883ae07 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmContextListener.java @@ -144,7 +144,12 @@ public class ScmContextListener extends GuiceServletContextListener groupManager.init(context); // init Authenticator - injector.getInstance(AuthenticationManager.class).init(context); + AuthenticationManager authenticationManager = + injector.getInstance(AuthenticationManager.class); + + authenticationManager.init(context); + authenticationManager.addListeners( + bindExtProcessor.getAuthenticationListeners()); return injector; } diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java index b56bf93b9b..df08016332 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java @@ -60,7 +60,7 @@ import javax.servlet.http.HttpServletResponse; * @author Sebastian Sdorra */ @Singleton -public class ChainAuthenticatonManager implements AuthenticationManager +public class ChainAuthenticatonManager extends AbstractAuthenticationManager { /** the logger for ChainAuthenticatonManager */ @@ -124,6 +124,9 @@ public class ChainAuthenticatonManager implements AuthenticationManager { user = result.getUser(); user.setType(authenticator.getType()); + + // notify authentication listeners + fireAuthenticationEvent(request, response, user); } break; From 2a407f65f7203497c21b1e2e860d7e61ff11b821 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Jan 2011 13:12:26 +0100 Subject: [PATCH 2/8] added change password dialog --- .../sonia/scm/api/rest/RestActionResult.java | 95 ++++++++++ .../resources/ChangePasswordResource.java | 170 ++++++++++++++++++ scm-webapp/src/main/webapp/index.html | 1 + .../main/webapp/resources/js/sonia.action.js | 125 +++++++++++++ .../src/main/webapp/resources/js/sonia.scm.js | 52 ++++-- 5 files changed, 430 insertions(+), 13 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/RestActionResult.java create mode 100644 scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java create mode 100644 scm-webapp/src/main/webapp/resources/js/sonia.action.js diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/RestActionResult.java b/scm-webapp/src/main/java/sonia/scm/api/rest/RestActionResult.java new file mode 100644 index 0000000000..ab806f3498 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/RestActionResult.java @@ -0,0 +1,95 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.api.rest; + +//~--- JDK imports ------------------------------------------------------------ + +import javax.xml.bind.annotation.XmlRootElement; + +/** + * + * @author Sebastian Sdorra + */ +@XmlRootElement(name = "result") +public class RestActionResult +{ + + /** + * Constructs ... + * + */ + public RestActionResult() {} + + /** + * Constructs ... + * + * + * @param success + */ + public RestActionResult(boolean success) + { + this.success = success; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @return + */ + public boolean isSuccess() + { + return success; + } + + //~--- set methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param success + */ + public void setSuccess(boolean success) + { + this.success = success; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private boolean success = false; +} 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 new file mode 100644 index 0000000000..52bb1aa595 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/ChangePasswordResource.java @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.api.rest.resources; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.inject.Inject; +import com.google.inject.Provider; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import sonia.scm.api.rest.RestActionResult; +import sonia.scm.security.EncryptionHandler; +import sonia.scm.user.User; +import sonia.scm.user.UserException; +import sonia.scm.user.UserManager; +import sonia.scm.user.xml.XmlUserManager; +import sonia.scm.util.AssertUtil; +import sonia.scm.web.security.WebSecurityContext; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import javax.ws.rs.FormParam; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * + * @author Sebastian Sdorra + */ +@Path("action/change-password") +public class ChangePasswordResource +{ + + /** the logger for ChangePasswordResource */ + private static final Logger logger = + LoggerFactory.getLogger(ChangePasswordResource.class); + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param userManager + * @param encryptionHandler + * @param securityContextProvider + */ + @Inject + public ChangePasswordResource( + UserManager userManager, EncryptionHandler encryptionHandler, + Provider securityContextProvider) + { + this.userManager = userManager; + this.encryptionHandler = encryptionHandler; + this.securityContextProvider = securityContextProvider; + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param oldPassword + * @param newPassword + * + * @return + * + * @throws IOException + * @throws UserException + */ + @POST + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public Response changePassword(@FormParam("old-password") String oldPassword, + @FormParam("new-password") String newPassword) + throws UserException, IOException + { + AssertUtil.assertIsNotEmpty(oldPassword); + AssertUtil.assertIsNotEmpty(newPassword); + + int length = newPassword.length(); + + if ((length < 6) || (length > 32)) + { + throw new WebApplicationException(Response.Status.BAD_REQUEST); + } + + Response response = null; + WebSecurityContext securityContext = securityContextProvider.get(); + User currentUser = securityContext.getUser(); + + if (logger.isInfoEnabled()) + { + logger.info("password change for user {}", currentUser.getName()); + } + + if (currentUser.getType().equals(XmlUserManager.TYPE)) + { + User dbUser = userManager.get(currentUser.getName()); + + if (encryptionHandler.encrypt(oldPassword).equals(dbUser.getPassword())) + { + dbUser.setPassword(encryptionHandler.encrypt(newPassword)); + userManager.modify(dbUser); + response = Response.ok(new RestActionResult(true)).build(); + } + else + { + response = Response.status(Response.Status.BAD_REQUEST).build(); + } + } + else + { + logger.error("only xml user can change their passwor"); + response = Response.status(Response.Status.INTERNAL_SERVER_ERROR).build(); + } + + return response; + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private EncryptionHandler encryptionHandler; + + /** Field description */ + private Provider securityContextProvider; + + /** Field description */ + private UserManager userManager; +} diff --git a/scm-webapp/src/main/webapp/index.html b/scm-webapp/src/main/webapp/index.html index b7ce4c6ccb..33a45f3050 100644 --- a/scm-webapp/src/main/webapp/index.html +++ b/scm-webapp/src/main/webapp/index.html @@ -58,6 +58,7 @@ + diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.action.js b/scm-webapp/src/main/webapp/resources/js/sonia.action.js new file mode 100644 index 0000000000..da064fd70e --- /dev/null +++ b/scm-webapp/src/main/webapp/resources/js/sonia.action.js @@ -0,0 +1,125 @@ +/* * + * Copyright (c) 2010, Sebastian Sdorra + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of SCM-Manager; nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + +Ext.ns('Sonia.action'); + +Sonia.action.ChangePasswordWindow = Ext.extend(Ext.Window,{ + + initComponent: function(){ + + var config = { + layout:'fit', + width:300, + height:170, + closable: false, + resizable: false, + plain: true, + border: false, + modal: true, + items: [{ + id: 'changePasswordForm', + url: restUrl + 'action/change-password.json', + title: 'Change Password', + frame: true, + xtype: 'form', + monitorValid: true, + defaultType: 'textfield', + items: [{ + name: 'old-password', + fieldLabel: 'Old Password', + inputType: 'password', + allowBlank: false, + minLength: 6, + maxLength: 32 + },{ + id: 'new-password', + name: 'new-password', + fieldLabel: 'New Password', + inputType: 'password', + allowBlank: false, + minLength: 6, + maxLength: 32 + },{ + name: 'confirm-password', + fieldLabel: 'Confirm Password', + inputType: 'password', + allowBlank: false, + minLength: 6, + maxLength: 32, + vtype: 'password', + initialPassField: 'new-password' + }], + buttons: [{ + text: 'Ok', + formBind: true, + scope: this, + handler: this.changePassword + },{ + text: 'Cancel', + scope: this, + handler: this.cancel + }] + }] + } + + Ext.apply(this, Ext.apply(this.initialConfig, config)); + Sonia.action.ChangePasswordWindow.superclass.initComponent.apply(this, arguments); + }, + + changePassword: function(){ + var win = this; + var form = Ext.getCmp('changePasswordForm').getForm(); + form.submit({ + method:'POST', + waitTitle:'Connecting', + waitMsg:'Sending data...', + + success: function(form, action){ + if ( debug ){ + console.debug( 'change password success' ); + } + win.close(); + }, + + failure: function(form, action){ + if ( debug ){ + console.debug( 'change password failed' ); + } + Ext.Msg.alert('change password failed!'); + } + }); + }, + + cancel: function(){ + this.close(); + } + +}); \ No newline at end of file diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.scm.js b/scm-webapp/src/main/webapp/resources/js/sonia.scm.js index 1e023056e3..b443c7cf77 100644 --- a/scm-webapp/src/main/webapp/resources/js/sonia.scm.js +++ b/scm-webapp/src/main/webapp/resources/js/sonia.scm.js @@ -120,7 +120,22 @@ Ext.onReady(function(){ }] }); + var securitySection = null; + + if ( state.user.type == 'xml' && state.user.name != 'anonymous' ){ + securitySection = { + title: 'Security', + items: [{ + label: 'Change Password', + fn: function(){ + new Sonia.action.ChangePasswordWindow().show(); + } + }] + } + } + if ( admin ){ + panel.addSections([{ id: 'navConfig', title: 'Config', @@ -140,20 +155,31 @@ Ext.onReady(function(){ addTabPanel('plugins', 'pluginGrid', 'Plugins'); } }] - },{ - title: 'Security', - items: [{ - label: 'Users', - fn: function(){ - addTabPanel('users', 'userPanel', 'Users'); - } - },{ - label: 'Groups', - fn: function(){ - addTabPanel('groups', 'groupPanel', 'Groups'); - } - }] }]); + + if ( securitySection == null ){ + securitySection = { + title: 'Security', + items: [] + } + } + + securitySection.items.push({ + label: 'Users', + fn: function(){ + addTabPanel('users', 'userPanel', 'Users'); + } + }); + securitySection.items.push({ + label: 'Groups', + fn: function(){ + addTabPanel('groups', 'groupPanel', 'Groups'); + } + }); + } + + if ( securitySection != null ){ + panel.addSection( securitySection ); } if ( state.user.name == 'anonymous' ){ From 426f3fe5257cfb05288aa707d2ae522f2ea7a9d3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Jan 2011 16:34:20 +0100 Subject: [PATCH 3/8] added auto configuration button to mercurial config panel --- .../api/rest/resources/HgConfigResource.java | 21 +++++++++++++++++++ .../scm/repository/HgRepositoryHandler.java | 16 ++++++++++---- .../src/main/resources/sonia/scm/hg.config.js | 17 +++++++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java b/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java index 674100db1e..4ac5bc18e2 100644 --- a/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java +++ b/plugins/scm-hg-plugin/src/main/java/sonia/scm/api/rest/resources/HgConfigResource.java @@ -78,6 +78,27 @@ public class HgConfigResource this.handler = handler; } + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param uriInfo + * + * @return + */ + @POST + @Path("auto-configuration") + @Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON }) + public HgConfig autoConfiguration(@Context UriInfo uriInfo) + { + handler.setConfig(null); + handler.doAutoConfiguration(); + + return handler.getConfig(); + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java b/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java index 7d031d4881..aebaabdaf7 100644 --- a/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java +++ b/plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/HgRepositoryHandler.java @@ -101,11 +101,8 @@ public class HgRepositoryHandler * Method description * */ - @Override - public void loadConfig() + public void doAutoConfiguration() { - super.loadConfig(); - HgInstaller installer = null; if (SystemUtil.isWindows()) @@ -163,6 +160,17 @@ public class HgRepositoryHandler storeConfig(); } + /** + * Method description + * + */ + @Override + public void loadConfig() + { + super.loadConfig(); + doAutoConfiguration(); + } + //~--- get methods ---------------------------------------------------------- /** diff --git a/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js b/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js index 875b34422d..0532890ece 100644 --- a/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js +++ b/plugins/scm-hg-plugin/src/main/resources/sonia/scm/hg.config.js @@ -30,6 +30,7 @@ */ registerConfigPanel({ + id: 'hgConfigForm', xtype : 'configForm', title : 'Mercurial Settings', items : [{ @@ -56,6 +57,14 @@ registerConfigPanel({ name: 'useOptimizedBytecode', fieldLabel: 'Optimized Bytecode (.pyo)', inputValue: 'true' + },{ + xtype: 'button', + text: 'Load Auto-Configuration', + fieldLabel: 'Auto-Configuration', + handler: function(){ + var self = Ext.getCmp('hgConfigForm'); + self.loadConfig( self.el, 'config/repositories/hg/auto-configuration.json', 'POST' ); + } }], onSubmit: function(values){ @@ -76,10 +85,14 @@ registerConfigPanel({ }, onLoad: function(el){ + this.loadConfig(el, 'config/repositories/hg.json', 'GET'); + }, + + loadConfig: function(el, url, method){ var tid = setTimeout( function(){ el.mask('Loading ...'); }, 100); Ext.Ajax.request({ - url: restUrl + 'config/repositories/hg.json', - method: 'GET', + url: restUrl + url, + method: method, scope: this, disableCaching: true, success: function(response){ From adf7fae4461861063708ab9e9b9beab7149f18d0 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Jan 2011 18:15:11 +0100 Subject: [PATCH 4/8] changes for issue '#2 Support applying permissions to groups as well as users' --- .../src/main/java/sonia/scm/group/Group.java | 13 +++++ .../java/sonia/scm/group/GroupManager.java | 19 ++++++- .../java/sonia/scm/repository/Permission.java | 40 +++++++++++++ .../sonia/scm/repository/PermissionUtil.java | 18 ++++-- .../scm/web/security/WebSecurityContext.java | 10 ++++ .../scm/repository/PermissionUtilTest.java | 47 +++++++++++++++ .../web/security/DummyWebSecurityContext.java | 18 ++++++ .../sonia/scm/group/xml/XmlGroupManager.java | 25 ++++++++ .../web/security/BasicSecurityContext.java | 57 +++++++++++++++++++ 9 files changed, 239 insertions(+), 8 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/group/Group.java b/scm-core/src/main/java/sonia/scm/group/Group.java index c1bc83f4f5..9235ecb006 100644 --- a/scm-core/src/main/java/sonia/scm/group/Group.java +++ b/scm-core/src/main/java/sonia/scm/group/Group.java @@ -377,6 +377,19 @@ public class Group return type; } + /** + * Method description + * + * + * @param member + * + * @return + */ + public boolean isMember(String member) + { + return (members != null) && members.contains(member); + } + /** * Method description * diff --git a/scm-core/src/main/java/sonia/scm/group/GroupManager.java b/scm-core/src/main/java/sonia/scm/group/GroupManager.java index aa2c86b548..93c2795a33 100644 --- a/scm-core/src/main/java/sonia/scm/group/GroupManager.java +++ b/scm-core/src/main/java/sonia/scm/group/GroupManager.java @@ -38,10 +38,25 @@ package sonia.scm.group; import sonia.scm.ListenerSupport; import sonia.scm.Manager; +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collection; + /** * * @author Sebastian Sdorra */ public interface GroupManager - extends Manager, - ListenerSupport {} + extends Manager, ListenerSupport +{ + + /** + * Method description + * + * + * @param member + * + * @return + */ + public Collection getGroupsForMember(String member); +} diff --git a/scm-core/src/main/java/sonia/scm/repository/Permission.java b/scm-core/src/main/java/sonia/scm/repository/Permission.java index 856ce8096b..50305239d1 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Permission.java +++ b/scm-core/src/main/java/sonia/scm/repository/Permission.java @@ -85,6 +85,21 @@ public class Permission implements Serializable this.type = type; } + /** + * Constructs ... + * + * + * @param name + * @param groupPermission + * @param type + */ + public Permission(String name, boolean groupPermission, PermissionType type) + { + this.name = name; + this.groupPermission = groupPermission; + this.type = type; + } + //~--- get methods ---------------------------------------------------------- /** @@ -109,8 +124,30 @@ public class Permission implements Serializable return type; } + /** + * Method description + * + * + * @return + */ + public boolean isGroupPermission() + { + return groupPermission; + } + //~--- set methods ---------------------------------------------------------- + /** + * Method description + * + * + * @param groupPermission + */ + public void setGroupPermission(boolean groupPermission) + { + this.groupPermission = groupPermission; + } + /** * Method description * @@ -135,6 +172,9 @@ public class Permission implements Serializable //~--- fields --------------------------------------------------------------- + /** Field description */ + private boolean groupPermission = false; + /** Field description */ private String name; 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 85526cdf3b..595cb5d836 100644 --- a/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java +++ b/scm-core/src/main/java/sonia/scm/repository/PermissionUtil.java @@ -43,6 +43,7 @@ import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collection; import java.util.List; /** @@ -134,7 +135,8 @@ public class PermissionUtil if (permissions != null) { - result = hasPermission(permissions, username, pt); + result = hasPermission(permissions, username, + securityContext.getGroups(), pt); } } @@ -147,12 +149,13 @@ public class PermissionUtil * * @param permissions * @param username + * @param groups * @param pt * * @return */ private static boolean hasPermission(List permissions, - String username, PermissionType pt) + String username, Collection groups, PermissionType pt) { boolean result = false; @@ -160,12 +163,15 @@ public class PermissionUtil { String name = p.getName(); - if ((name != null) && name.equalsIgnoreCase(username) - && (p.getType().getValue() >= pt.getValue())) + if ((name != null) && (p.getType().getValue() >= pt.getValue())) { - result = true; + if (name.equals(username) + || (p.isGroupPermission() && groups.contains(p.getName()))) + { + result = true; - break; + break; + } } } diff --git a/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java b/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java index d113ae672b..1b9ccec280 100644 --- a/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java +++ b/scm-core/src/main/java/sonia/scm/web/security/WebSecurityContext.java @@ -40,6 +40,8 @@ import sonia.scm.user.User; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collection; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -76,6 +78,14 @@ public interface WebSecurityContext extends SecurityContext //~--- get methods ---------------------------------------------------------- + /** + * Method description + * + * + * @return + */ + public Collection getGroups(); + /** * Method description * diff --git a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java index 676920ff20..5b56d42f62 100644 --- a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java @@ -50,7 +50,10 @@ import static org.mockito.Mockito.*; //~--- JDK imports ------------------------------------------------------------ +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; /** * @@ -108,6 +111,30 @@ public class PermissionUtilTest PermissionUtil.assertPermission(repository, admams, PermissionType.OWNER); } + /** + * Method description + * + */ + @Test + public void testGroupPermissions() + { + WebSecurityContext context = mockGroupCtx(new User("dent", "Arthur Dent", + "arthur.dent@hitchhiker.com")); + Repository r = new Repository(); + + r.setPermissions( + new ArrayList( + Arrays.asList( + new Permission("dent"), + new Permission("devel", true, PermissionType.READ), + new Permission("qa", true, PermissionType.WRITE)))); + assertTrue(PermissionUtil.hasPermission(r, context, PermissionType.READ)); + assertTrue(PermissionUtil.hasPermission(r, context, PermissionType.WRITE)); + assertFalse(PermissionUtil.hasPermission(r, context, PermissionType.OWNER)); + r.getPermissions().add(new Permission("dent", PermissionType.OWNER)); + assertTrue(PermissionUtil.hasPermission(r, context, PermissionType.OWNER)); + } + //~--- get methods ---------------------------------------------------------- /** @@ -162,6 +189,26 @@ public class PermissionUtilTest return context; } + /** + * Method description + * + * + * @param user + * + * @return + */ + private WebSecurityContext mockGroupCtx(User user) + { + WebSecurityContext context = mockCtx(user); + Set groups = new HashSet(); + + groups.add("devel"); + groups.add("qa"); + when(context.getGroups()).thenReturn(groups); + + return context; + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-test/src/main/java/sonia/scm/web/security/DummyWebSecurityContext.java b/scm-test/src/main/java/sonia/scm/web/security/DummyWebSecurityContext.java index 9ec7ab1409..467a6dbe3a 100644 --- a/scm-test/src/main/java/sonia/scm/web/security/DummyWebSecurityContext.java +++ b/scm-test/src/main/java/sonia/scm/web/security/DummyWebSecurityContext.java @@ -39,6 +39,9 @@ import sonia.scm.user.User; //~--- JDK imports ------------------------------------------------------------ +import java.util.HashSet; +import java.util.Set; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -97,6 +100,18 @@ public class DummyWebSecurityContext implements WebSecurityContext //~--- get methods ---------------------------------------------------------- + /** + * Method description + * + * + * @return + */ + @Override + public Set getGroups() + { + return groups; + } + /** * Method description * @@ -123,6 +138,9 @@ public class DummyWebSecurityContext implements WebSecurityContext //~--- fields --------------------------------------------------------------- + /** Field description */ + private Set groups = new HashSet(); + /** Field description */ private User user; } diff --git a/scm-webapp/src/main/java/sonia/scm/group/xml/XmlGroupManager.java b/scm-webapp/src/main/java/sonia/scm/group/xml/XmlGroupManager.java index cad532084f..9a6a747e12 100644 --- a/scm-webapp/src/main/java/sonia/scm/group/xml/XmlGroupManager.java +++ b/scm-webapp/src/main/java/sonia/scm/group/xml/XmlGroupManager.java @@ -60,6 +60,7 @@ import java.io.IOException; import java.util.Collection; import java.util.LinkedList; +import java.util.Set; /** * @@ -323,6 +324,30 @@ public class XmlGroupManager extends AbstractGroupManager return groups; } + /** + * Method description + * + * + * @param member + * + * @return + */ + @Override + public Collection getGroupsForMember(String member) + { + LinkedList groups = new LinkedList(); + + for (Group group : groupDB.values()) + { + if (group.isMember(member)) + { + groups.add(group.clone()); + } + } + + return groups; + } + //~--- methods -------------------------------------------------------------- /** 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 b8c7ba5336..91f4cf4743 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 @@ -42,11 +42,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.config.ScmConfiguration; +import sonia.scm.group.Group; +import sonia.scm.group.GroupManager; import sonia.scm.user.User; import sonia.scm.user.UserManager; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -74,15 +80,18 @@ public class BasicSecurityContext implements WebSecurityContext * * @param configuration * @param authenticator + * @param groupManager * @param userManager */ @Inject public BasicSecurityContext(ScmConfiguration configuration, AuthenticationManager authenticator, + GroupManager groupManager, UserManager userManager) { this.configuration = configuration; this.authenticator = authenticator; + this.groupManager = groupManager; this.userManager = userManager; } @@ -128,6 +137,8 @@ public class BasicSecurityContext implements WebSecurityContext { userManager.create(user); } + + loadGroups(); } catch (Exception ex) { @@ -150,10 +161,28 @@ public class BasicSecurityContext implements WebSecurityContext public void logout(HttpServletRequest request, HttpServletResponse response) { user = null; + groups = null; } //~--- get methods ---------------------------------------------------------- + /** + * Method description + * + * + * @return + */ + @Override + public Collection getGroups() + { + if (groups == null) + { + groups = new HashSet(); + } + + return groups; + } + /** * Method description * @@ -183,6 +212,28 @@ public class BasicSecurityContext implements WebSecurityContext return getUser() != null; } + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + */ + private void loadGroups() + { + groups = new HashSet(); + + Collection groupCollection = + groupManager.getGroupsForMember(user.getName()); + + if (groupCollection != null) + { + for (Group group : groupCollection) + { + groups.add(group.getName()); + } + } + } + //~--- fields --------------------------------------------------------------- /** Field description */ @@ -191,6 +242,12 @@ public class BasicSecurityContext implements WebSecurityContext /** Field description */ private ScmConfiguration configuration; + /** Field description */ + private GroupManager groupManager; + + /** Field description */ + private Set groups = new HashSet(); + /** Field description */ private User user; From 1256ff79599b9ec854f061fcb9e5dfa0ef7f6260 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Jan 2011 18:26:37 +0100 Subject: [PATCH 5/8] added groups to state --- .../src/main/java/sonia/scm/ScmState.java | 43 ++++++++++++++++--- .../resources/AuthenticationResource.java | 26 ++--------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/scm-webapp/src/main/java/sonia/scm/ScmState.java b/scm-webapp/src/main/java/sonia/scm/ScmState.java index c4e6210593..5565038507 100644 --- a/scm-webapp/src/main/java/sonia/scm/ScmState.java +++ b/scm-webapp/src/main/java/sonia/scm/ScmState.java @@ -36,9 +36,12 @@ package sonia.scm; //~--- non-JDK imports -------------------------------------------------------- import sonia.scm.user.User; +import sonia.scm.web.security.WebSecurityContext; //~--- JDK imports ------------------------------------------------------------ +import java.util.Collection; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -63,12 +66,15 @@ public class ScmState * Constructs ... * * - * @param user + * + * @param securityContext * @param repositoryTypes */ - public ScmState(User user, Type[] repositoryTypes) + public ScmState(WebSecurityContext securityContext, + Collection repositoryTypes) { - this.user = user; + this.user = securityContext.getUser(); + this.groups = securityContext.getGroups(); this.repositoryTypes = repositoryTypes; } @@ -80,7 +86,18 @@ public class ScmState * * @return */ - public Type[] getRepositoryTypes() + public Collection getGroups() + { + return groups; + } + + /** + * Method description + * + * + * @return + */ + public Collection getRepositoryTypes() { return repositoryTypes; } @@ -109,13 +126,24 @@ public class ScmState //~--- set methods ---------------------------------------------------------- + /** + * Method description + * + * + * @param groups + */ + public void setGroups(Collection groups) + { + this.groups = groups; + } + /** * Method description * * * @param repositoryTypes */ - public void setRepositoryTypes(Type[] repositoryTypes) + public void setRepositoryTypes(Collection repositoryTypes) { this.repositoryTypes = repositoryTypes; } @@ -144,9 +172,12 @@ public class ScmState //~--- fields --------------------------------------------------------------- + /** Field description */ + private Collection groups; + /** Field description */ @XmlElement(name = "repositoryTypes") - private Type[] repositoryTypes; + private Collection repositoryTypes; /** Field description */ private boolean success = true; 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 4bc0b5c186..52e9c71ad7 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 @@ -99,7 +99,8 @@ public class AuthenticationResource if (user != null) { - resp = Response.ok(getState(user)).build(); + resp = Response.ok(new ScmState(securityContext, + repositoryManger.getTypes())).build(); } else { @@ -135,7 +136,7 @@ public class AuthenticationResource if (user != null) { - state = getState(user); + state = new ScmState(securityContext, repositoryManger.getTypes()); } else { @@ -167,7 +168,7 @@ public class AuthenticationResource logger.debug("return state for user {}", user.getName()); } - state = getState(user); + state = new ScmState(securityContext, repositoryManger.getTypes()); response = Response.ok(state).build(); } else @@ -178,25 +179,6 @@ public class AuthenticationResource return response; } - /** - * Method description - * - * - * - * @param user - * - * @return - */ - private ScmState getState(User user) - { - ScmState state = new ScmState(); - - state.setUser(user); - state.setRepositoryTypes(repositoryManger.getTypes().toArray(new Type[0])); - - return state; - } - //~--- fields --------------------------------------------------------------- /** Field description */ From 2b74b1c8252c8ba74bde65c6ca010cbcde1a2ac4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 7 Jan 2011 19:32:08 +0100 Subject: [PATCH 6/8] added groupPermission attribute to repository permission grid --- .../webapp/resources/js/sonia.repository.js | 57 +++++++++++++++++-- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/scm-webapp/src/main/webapp/resources/js/sonia.repository.js b/scm-webapp/src/main/webapp/resources/js/sonia.repository.js index 406f9f1725..2cf6051876 100644 --- a/scm-webapp/src/main/webapp/resources/js/sonia.repository.js +++ b/scm-webapp/src/main/webapp/resources/js/sonia.repository.js @@ -46,6 +46,35 @@ Ext.ns('Sonia.repository'); // functions +Sonia.repository.getPermissionValue = function(type){ + var value = 0; + switch (type){ + case "READ": + value = 0; + break; + case "WRITE": + value = 10; + break; + case "OWNER": + value = 100; + break; + } + return value; +} + +Sonia.repository.isMember = function(group){ + var result = false; + if ( Ext.isDefined(state.groups) ){ + for ( var i=0; i= value ){ + if ( p.groupPermission ){ + if ( Sonia.repository.isMember( p.name ) ){ + result = true; + break; + } + } else if ( p.name == state.user.name ) { + result = true; + break; + } } } } @@ -187,7 +226,7 @@ Sonia.repository.FormPanel = Ext.extend(Sonia.rest.FormPanel,{ this.permissionStore = new Ext.data.JsonStore({ root: 'permissions', - fields: [ 'name', 'type' ], + fields: [ 'name', 'type', 'groupPermission' ], sortInfo: { field: 'name' } @@ -208,6 +247,7 @@ Sonia.repository.FormPanel = Ext.extend(Sonia.rest.FormPanel,{ id: 'type', header: 'Type', dataIndex: 'type', + width: 80, editor: new Ext.form.ComboBox({ valueField: 'type', displayField: 'type', @@ -224,8 +264,13 @@ Sonia.repository.FormPanel = Ext.extend(Sonia.rest.FormPanel,{ ] }) }) - } - ] + },{ + id: 'groupPermission', + header: 'Group', + dataIndex: 'groupPermission', + width: 60, + editor: new Ext.form.Checkbox() + }] }); if ( update ){ From 5f29c02ce678cd168f21eb0cb00aa55edab2d9f3 Mon Sep 17 00:00:00 2001 From: "David M. Carr" Date: Fri, 7 Jan 2011 17:11:24 -0500 Subject: [PATCH 7/8] Enhance unit test for PermissionUtil to cover more group permission scenarios. Reduce duplication of code in Permission constructors. --- .../java/sonia/scm/repository/Permission.java | 10 ++-- .../scm/repository/PermissionUtilTest.java | 49 +++++++++++++------ 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/repository/Permission.java b/scm-core/src/main/java/sonia/scm/repository/Permission.java index 50305239d1..a446717cb9 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Permission.java +++ b/scm-core/src/main/java/sonia/scm/repository/Permission.java @@ -69,6 +69,7 @@ public class Permission implements Serializable */ public Permission(String name) { + this(); this.name = name; } @@ -81,7 +82,7 @@ public class Permission implements Serializable */ public Permission(String name, PermissionType type) { - this.name = name; + this(name); this.type = type; } @@ -90,14 +91,13 @@ public class Permission implements Serializable * * * @param name - * @param groupPermission * @param type + * @param groupPermission */ - public Permission(String name, boolean groupPermission, PermissionType type) + public Permission(String name, PermissionType type, boolean groupPermission) { - this.name = name; + this(name, type); this.groupPermission = groupPermission; - this.type = type; } //~--- get methods ---------------------------------------------------------- diff --git a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java index 5b56d42f62..2ebb29622e 100644 --- a/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java +++ b/scm-core/src/test/java/sonia/scm/repository/PermissionUtilTest.java @@ -118,21 +118,44 @@ public class PermissionUtilTest @Test public void testGroupPermissions() { - WebSecurityContext context = mockGroupCtx(new User("dent", "Arthur Dent", - "arthur.dent@hitchhiker.com")); + WebSecurityContext dent = mockGroupCtx(new User("dent", "Arthur Dent", + "arthur.dent@hitchhiker.com"), + "devel", "qa"); + WebSecurityContext ford = mockGroupCtx(new User("ford", "Ford Prefect", + "ford.prefect@hitchhiker.com"), "devel"); + WebSecurityContext zaphod = mockGroupCtx(new User("zaphod", + "Zaphod Beeblebrox", + "zaphod.beeblebrox@hitchhiker.com"), "qa"); + WebSecurityContext trillian = mockGroupCtx(new User("trillian", + "Trillian Astra", + "trillian.astra@hitchhiker.com")); Repository r = new Repository(); r.setPermissions( new ArrayList( Arrays.asList( new Permission("dent"), - new Permission("devel", true, PermissionType.READ), - new Permission("qa", true, PermissionType.WRITE)))); - assertTrue(PermissionUtil.hasPermission(r, context, PermissionType.READ)); - assertTrue(PermissionUtil.hasPermission(r, context, PermissionType.WRITE)); - assertFalse(PermissionUtil.hasPermission(r, context, PermissionType.OWNER)); + new Permission("devel", PermissionType.WRITE, true), + new Permission("qa", PermissionType.READ, true)))); + // member of both devel and qa + assertTrue(PermissionUtil.hasPermission(r, dent, PermissionType.READ)); + assertTrue(PermissionUtil.hasPermission(r, dent, PermissionType.WRITE)); + assertFalse(PermissionUtil.hasPermission(r, dent, PermissionType.OWNER)); + // now, additionally the owner r.getPermissions().add(new Permission("dent", PermissionType.OWNER)); - assertTrue(PermissionUtil.hasPermission(r, context, PermissionType.OWNER)); + assertTrue(PermissionUtil.hasPermission(r, dent, PermissionType.OWNER)); + // member of just devel + assertTrue(PermissionUtil.hasPermission(r, ford, PermissionType.READ)); + assertTrue(PermissionUtil.hasPermission(r, ford, PermissionType.WRITE)); + assertFalse(PermissionUtil.hasPermission(r, ford, PermissionType.OWNER)); + // member of just qa + assertTrue(PermissionUtil.hasPermission(r, zaphod, PermissionType.READ)); + assertFalse(PermissionUtil.hasPermission(r, zaphod, PermissionType.WRITE)); + assertFalse(PermissionUtil.hasPermission(r, zaphod, PermissionType.OWNER)); + // member of no groups + assertFalse(PermissionUtil.hasPermission(r, trillian, PermissionType.READ)); + assertFalse(PermissionUtil.hasPermission(r, trillian, PermissionType.WRITE)); + assertFalse(PermissionUtil.hasPermission(r, trillian, PermissionType.OWNER)); } //~--- get methods ---------------------------------------------------------- @@ -197,14 +220,12 @@ public class PermissionUtilTest * * @return */ - private WebSecurityContext mockGroupCtx(User user) + private WebSecurityContext mockGroupCtx(User user, String... groups) { WebSecurityContext context = mockCtx(user); - Set groups = new HashSet(); - - groups.add("devel"); - groups.add("qa"); - when(context.getGroups()).thenReturn(groups); + + Set groupSet = new HashSet(Arrays.asList(groups)); + when(context.getGroups()).thenReturn(groupSet); return context; } From 37a9c506d9248374337df99f109442de984724c8 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Sat, 8 Jan 2011 13:04:04 +0100 Subject: [PATCH 8/8] improve authentication api for issue '#3 Add extension point for plugins to define groups and their members' --- .../scm/pam/PAMAuthenticationHandler.java | 2 +- .../web/security/AuthenticationManager.java | 6 +-- .../web/security/AuthenticationResult.java | 49 +++++++++++++++++++ .../web/security/BasicSecurityContext.java | 49 +++++++++++++++++-- .../security/ChainAuthenticatonManager.java | 13 ++--- .../ChainAuthenticationManagerTest.java | 22 ++++----- 6 files changed, 114 insertions(+), 27 deletions(-) diff --git a/plugins/scm-pam-plugin/src/main/java/sonia/scm/pam/PAMAuthenticationHandler.java b/plugins/scm-pam-plugin/src/main/java/sonia/scm/pam/PAMAuthenticationHandler.java index 1cf1c87311..207961eda6 100644 --- a/plugins/scm-pam-plugin/src/main/java/sonia/scm/pam/PAMAuthenticationHandler.java +++ b/plugins/scm-pam-plugin/src/main/java/sonia/scm/pam/PAMAuthenticationHandler.java @@ -138,7 +138,7 @@ public class PAMAuthenticationHandler implements AuthenticationHandler User user = new User(username); user.setAdmin(isAdmin(unixUser)); - result = new AuthenticationResult(user); + result = new AuthenticationResult(user, unixUser.getGroups()); } } catch (PAMException ex) diff --git a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java index d0abd7b69a..1951a9d35d 100644 --- a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java +++ b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationManager.java @@ -37,7 +37,6 @@ package sonia.scm.web.security; import sonia.scm.Initable; import sonia.scm.ListenerSupport; -import sonia.scm.user.User; //~--- JDK imports ------------------------------------------------------------ @@ -65,7 +64,6 @@ public interface AuthenticationManager * * @return */ - public User authenticate(HttpServletRequest request, - HttpServletResponse response, String username, - String password); + public AuthenticationResult authenticate(HttpServletRequest request, + HttpServletResponse response, String username, String password); } diff --git a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationResult.java b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationResult.java index 5a581d3212..73bdf3a698 100644 --- a/scm-core/src/main/java/sonia/scm/web/security/AuthenticationResult.java +++ b/scm-core/src/main/java/sonia/scm/web/security/AuthenticationResult.java @@ -37,6 +37,10 @@ package sonia.scm.web.security; import sonia.scm.user.User; +//~--- JDK imports ------------------------------------------------------------ + +import java.util.Collection; + /** * * @author Sebastian Sdorra @@ -91,6 +95,37 @@ public class AuthenticationResult this.state = state; } + /** + * Constructs ... + * + * + * + * @param user + * @param groups + */ + public AuthenticationResult(User user, Collection groups) + { + this.user = user; + this.groups = groups; + this.state = AuthenticationState.SUCCESS; + } + + /** + * Constructs ... + * + * + * @param user + * @param groups + * @param state + */ + public AuthenticationResult(User user, Collection groups, + AuthenticationState state) + { + this.user = user; + this.groups = groups; + this.state = state; + } + //~--- methods -------------------------------------------------------------- /** @@ -118,6 +153,17 @@ public class AuthenticationResult //~--- get methods ---------------------------------------------------------- + /** + * Method description + * + * + * @return + */ + public Collection getGroups() + { + return groups; + } + /** * Method description * @@ -142,6 +188,9 @@ public class AuthenticationResult //~--- fields --------------------------------------------------------------- + /** Field description */ + private Collection groups; + /** Field description */ private AuthenticationState state; 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 91f4cf4743..86643ff23a 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 @@ -51,6 +51,7 @@ import sonia.scm.user.UserManager; import java.util.Collection; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import javax.servlet.http.HttpServletRequest; @@ -113,10 +114,13 @@ public class BasicSecurityContext implements WebSecurityContext HttpServletResponse response, String username, String password) { - user = authenticator.authenticate(request, response, username, password); + AuthenticationResult ar = authenticator.authenticate(request, response, + username, password); - if (user != null) + if (ar != null) { + user = ar.getUser(); + try { user.setLastLogin(System.currentTimeMillis()); @@ -138,7 +142,19 @@ public class BasicSecurityContext implements WebSecurityContext userManager.create(user); } + Collection groupCollection = ar.getGroups(); + + if (groupCollection != null) + { + groups.addAll(groupCollection); + } + loadGroups(); + + if (logger.isDebugEnabled()) + { + logGroups(); + } } catch (Exception ex) { @@ -161,7 +177,7 @@ public class BasicSecurityContext implements WebSecurityContext public void logout(HttpServletRequest request, HttpServletResponse response) { user = null; - groups = null; + groups = new HashSet(); } //~--- get methods ---------------------------------------------------------- @@ -220,8 +236,6 @@ public class BasicSecurityContext implements WebSecurityContext */ private void loadGroups() { - groups = new HashSet(); - Collection groupCollection = groupManager.getGroupsForMember(user.getName()); @@ -234,6 +248,31 @@ public class BasicSecurityContext implements WebSecurityContext } } + /** + * Method description + * + */ + private void logGroups() + { + StringBuilder msg = new StringBuilder("user "); + + msg.append(user.getName()).append(" is member of "); + + Iterator groupIt = groups.iterator(); + + while (groupIt.hasNext()) + { + msg.append(groupIt.next()); + + if (groupIt.hasNext()) + { + msg.append(", "); + } + } + + logger.debug(msg.toString()); + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java index df08016332..d814b1f5d3 100644 --- a/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java +++ b/scm-webapp/src/main/java/sonia/scm/web/security/ChainAuthenticatonManager.java @@ -97,11 +97,10 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager * @return */ @Override - public User authenticate(HttpServletRequest request, - HttpServletResponse response, String username, - String password) + public AuthenticationResult authenticate(HttpServletRequest request, + HttpServletResponse response, String username, String password) { - User user = null; + AuthenticationResult ar = null; for (AuthenticationHandler authenticator : authenticationHandlerSet) { @@ -122,8 +121,10 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager { if (result.getState().isSuccessfully() && (result.getUser() != null)) { - user = result.getUser(); + User user = result.getUser(); + user.setType(authenticator.getType()); + ar = result; // notify authentication listeners fireAuthenticationEvent(request, response, user); @@ -138,7 +139,7 @@ public class ChainAuthenticatonManager extends AbstractAuthenticationManager } } - return user; + return ar; } /** diff --git a/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java b/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java index 05b1b7e014..27810c5ad1 100644 --- a/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java +++ b/scm-webapp/src/test/java/sonia/scm/web/security/ChainAuthenticationManagerTest.java @@ -69,10 +69,10 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase @Test public void testAuthenticateFailed() { - User user = manager.authenticate(request, response, trillian.getName(), + AuthenticationResult result = manager.authenticate(request, response, trillian.getName(), "trillian"); - assertNull(user); + assertNull(result); } /** @@ -82,9 +82,9 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase @Test public void testAuthenticateNotFound() { - User user = manager.authenticate(request, response, "dent", "trillian"); + AuthenticationResult result = manager.authenticate(request, response, "dent", "trillian"); - assertNull(user); + assertNull(result); } /** @@ -94,17 +94,17 @@ public class ChainAuthenticationManagerTest extends AbstractTestBase @Test public void testAuthenticateSuccess() { - User user = manager.authenticate(request, response, trillian.getName(), + AuthenticationResult result = manager.authenticate(request, response, trillian.getName(), "trillian123"); - assertNotNull(user); - assertUserEquals(trillian, user); - assertEquals("trilliansType", user.getType()); - user = manager.authenticate(request, response, perfect.getName(), + assertNotNull(result); + assertUserEquals(trillian, result.getUser()); + assertEquals("trilliansType", result.getUser().getType()); + result = manager.authenticate(request, response, perfect.getName(), "perfect123"); assertNotNull(perfect); - assertUserEquals(perfect, user); - assertEquals("perfectsType", user.getType()); + assertUserEquals(perfect, result.getUser()); + assertEquals("perfectsType", result.getUser().getType()); } /**