replace scm-manager 1.x security api with apache shiro and use PasswordService for stronger password hashes

This commit is contained in:
Sebastian Sdorra
2014-12-14 12:26:03 +01:00
parent 876f501644
commit 4fa8e6e88a
32 changed files with 661 additions and 3974 deletions

View File

@@ -35,6 +35,7 @@ package sonia.scm.group;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Joiner;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@@ -156,6 +157,18 @@ public final class GroupNames implements Serializable, Iterable<String>
return collection.iterator(); return collection.iterator();
} }
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
return Joiner.on(", ").join(collection);
}
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**
@@ -172,5 +185,5 @@ public final class GroupNames implements Serializable, Iterable<String>
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private Collection<String> collection; private final Collection<String> collection;
} }

View File

@@ -1,90 +0,0 @@
/**
* 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.security;
/**
*
* @author Sebastian Sdorra
*/
public class EncryptionException extends RuntimeException
{
/** Field description */
private static final long serialVersionUID = -3733681356044140444L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
public EncryptionException()
{
super();
}
/**
* Constructs ...
*
*
* @param message
*/
public EncryptionException(String message)
{
super(message);
}
/**
* Constructs ...
*
*
* @param cause
*/
public EncryptionException(Throwable cause)
{
super(cause);
}
/**
* Constructs ...
*
*
* @param message
* @param cause
*/
public EncryptionException(String message, Throwable cause)
{
super(message, cause);
}
}

View File

@@ -1,52 +0,0 @@
/**
* 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.security;
/**
*
* @author Sebastian Sdorra
*/
public interface EncryptionHandler
{
/**
* Method description
*
*
* @param value
*
* @return
*/
public String encrypt(String value);
}

View File

@@ -1,87 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import sonia.scm.web.security.AuthenticationResult;
/**
* Login attempt handler.
*
* @author Sebastian Sdorra
* @since 1.34
*/
public interface LoginAttemptHandler
{
/**
* This method is called before the authentication procedure is invoked.
*
*
* @param token authentication token
*
* @throws AuthenticationException
*/
public void beforeAuthentication(AuthenticationToken token)
throws AuthenticationException;
/**
* Handle successful authentication.
*
*
* @param token authentication token
* @param result successful authentication result
*
* @throws AuthenticationException
*/
public void onSuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException;
/**
* Handle unsuccessful authentication.
*
*
* @param token authentication token
* @param result unsuccessful authentication result
*
* @throws AuthenticationException
*/
public void onUnsuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException;
}

View File

@@ -1,145 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.util.AssertUtil;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
*
* @author Sebastian Sdorra
*/
public class MessageDigestEncryptionHandler implements EncryptionHandler
{
/** Field description */
public static final String DEFAULT_DIGEST = "SHA-1";
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*/
public MessageDigestEncryptionHandler()
{
this.digest = DEFAULT_DIGEST;
}
/**
* Constructs ...
*
*
* @param digest
*/
public MessageDigestEncryptionHandler(String digest)
{
this.digest = digest;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param value
*
* @return
*/
@Override
public String encrypt(String value)
{
String result = null;
try
{
AssertUtil.assertIsNotEmpty(value);
MessageDigest messageDigest = MessageDigest.getInstance(digest);
result = encrypt(messageDigest, value);
}
catch (NoSuchAlgorithmException ex)
{
throw new EncryptionException(ex);
}
return result;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public String getDigest()
{
return digest;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param messageDigest
* @param value
*
* @return
*/
private String encrypt(MessageDigest messageDigest, String value)
{
messageDigest.reset();
messageDigest.update(value.getBytes());
return Util.toString(messageDigest.digest());
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String digest;
}

View File

@@ -1,69 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.PasswordAware;
/**
* Checks if a plain text password matches against a encrypted one and updates
* password objects.
*
* @author Sebastian Sdorra
* @since 1.13
*/
public interface PasswordHandler
{
/**
* Returns true if the given password
* is the same as store in the password object.
*
*
* @param passwordAware object that holds the encrypted password
* @param password plain text password to check
*
* @return true if the password matches the encrypted one
*/
public boolean matches(PasswordAware passwordAware, String password);
/**
* Updates the password of the given password object.
*
*
* @param passwordAware object to update
* @param password plain text password
*/
public void updatePassword(PasswordAware passwordAware, String password);
}

View File

@@ -73,6 +73,18 @@ public final class HttpUtil
/** Field description */ /** Field description */
public static final String ENCODING = "UTF-8"; public static final String ENCODING = "UTF-8";
/**
* authorization header
* @since 2.0.0
*/
public static final String HEADER_AUTHORIZATION = "Authorization";
/**
* Basic authorization scheme
* @since 2.0.0
*/
public static final String AUTHORIZATION_SCHEME_BASIC = "Basic";
/** /**
* location header * location header
* @since 1.43 * @since 1.43

View File

@@ -1,66 +0,0 @@
/**
* 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.event.ScmEventBus;
import sonia.scm.user.User;
//~--- JDK imports ------------------------------------------------------------
/**
* Abstract base class for {@link AuthenticationManager} implementations. This
* class implements the listener methods of the {@link AuthenticationManager}
* interface.
*
* @author Sebastian Sdorra
*/
public abstract class AbstractAuthenticationManager
implements AuthenticationManager
{
/**
*
* Send a {@link AuthenticationEvent} to the {@link ScmEventBus}.
*
* @param user successful authenticated user
*/
protected void fireAuthenticationEvent(User user)
{
ScmEventBus.getInstance().post(new AuthenticationEvent(user));
}
}

View File

@@ -1,137 +0,0 @@
/**
* 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 com.google.common.base.Objects;
import sonia.scm.event.Event;
import sonia.scm.user.User;
/**
* The authentication event is called after a successful authentication.
*
* @author Sebastian Sdorra
* @since 1.27
*/
@Event
public class AuthenticationEvent
{
/**
* Constructs a new authentication event.
*
*
* @param user successful authenticated user
*/
public AuthenticationEvent(User user)
{
this.user = user;
}
//~--- methods --------------------------------------------------------------
/**
* {@inheritDoc}
*
*
* @param obj
*
* @return
*/
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
if (getClass() != obj.getClass())
{
return false;
}
final AuthenticationEvent other = (AuthenticationEvent) obj;
return Objects.equal(user, other.user);
}
/**
* {@inheritDoc}
*
*
* @return
*/
@Override
public int hashCode()
{
return Objects.hashCode(user);
}
/**
* {@inheritDoc}
*
*
* @return
*/
@Override
public String toString()
{
// J+
return Objects.toStringHelper(this).add("user", user).toString();
// J+
}
//~--- get methods ----------------------------------------------------------
/**
* Returns the successful authenticated user.
*
*
* @return successful authenticated user
*/
public User getUser()
{
return user;
}
//~--- fields ---------------------------------------------------------------
/** successful authenticated user */
private User user;
}

View File

@@ -1,70 +0,0 @@
/**
* 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.Initable;
import sonia.scm.TypedObject;
import sonia.scm.plugin.ExtensionPoint;
//~--- JDK imports ------------------------------------------------------------
import java.io.Closeable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
@ExtensionPoint
public interface AuthenticationHandler extends Initable, Closeable, TypedObject
{
/**
* Method description
*
*
* @param request
* @param response
* @param username
* @param password
*
* @return
*/
public AuthenticationResult authenticate(HttpServletRequest request,
HttpServletResponse response, String username, String password);
}

View File

@@ -1,68 +0,0 @@
/**
* 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.Initable;
//~--- JDK imports ------------------------------------------------------------
import java.io.Closeable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
public interface AuthenticationManager
extends Initable, Closeable
{
/**
* Method description
*
*
* @param request
* @param response
* @param username
* @param password
*
* @return
*/
public AuthenticationResult authenticate(HttpServletRequest request,
HttpServletResponse response, String username, String password);
}

View File

@@ -1,231 +0,0 @@
/**
* 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.io.Serializable;
import java.util.Collection;
/**
*
* @author Sebastian Sdorra
*/
public class AuthenticationResult implements Serializable
{
/** Field description */
public static final AuthenticationResult NOT_FOUND =
new AuthenticationResult(AuthenticationState.NOT_FOUND);
/** Field description */
public static final AuthenticationResult FAILED =
new AuthenticationResult(AuthenticationState.FAILED);
/** Field description */
private static final long serialVersionUID = -1318200822398893598L;
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param state
*/
public AuthenticationResult(AuthenticationState state)
{
this.state = state;
}
/**
* Constructs ...
*
*
*
* @param user
*/
public AuthenticationResult(User user)
{
this.user = user;
this.state = AuthenticationState.SUCCESS;
}
/**
* Constructs ...
*
*
* @param user
* @param state
*/
public AuthenticationResult(User user, AuthenticationState state)
{
this.user = user;
this.state = state;
}
/**
* Constructs ...
*
*
*
* @param user
* @param groups
*/
public AuthenticationResult(User user, Collection<String> groups)
{
this.user = user;
this.groups = groups;
this.state = AuthenticationState.SUCCESS;
}
/**
* Constructs ...
*
*
* @param user
* @param groups
* @param state
*/
public AuthenticationResult(User user, Collection<String> groups,
AuthenticationState state)
{
this.user = user;
this.groups = groups;
this.state = state;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
StringBuilder out = new StringBuilder("user: ");
if (user != null)
{
out.append(user.getName());
}
else
{
out.append("null");
}
return out.append(", state: ").append(state.toString()).toString();
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public Collection<String> getGroups()
{
return groups;
}
/**
* Method description
*
*
* @return
*/
public AuthenticationState getState()
{
return state;
}
/**
* Method description
*
*
* @return
*/
public User getUser()
{
return user;
}
/**
* Method description
*
*
* @return
*/
public boolean isCacheable()
{
return cacheable;
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*
* @param cacheable
*/
public void setCacheable(boolean cacheable)
{
this.cacheable = cacheable;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private boolean cacheable = true;
/** Field description */
private Collection<String> groups;
/** Field description */
private AuthenticationState state;
/** Field description */
private User user;
}

View File

@@ -1,72 +0,0 @@
/**
* 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;
/**
*
* @author Sebastian Sdorra
*/
public enum AuthenticationState
{
SUCCESS(true), NOT_FOUND(false), FAILED(false);
/**
* Constructs ...
*
*
* @param successfully
*/
private AuthenticationState(boolean successfully)
{
this.successfully = successfully;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
public boolean isSuccessfully()
{
return successfully;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private boolean successfully = false;
}

View File

@@ -35,7 +35,7 @@
<name>scmadmin</name> <name>scmadmin</name>
<displayName>SCM Administrator</displayName> <displayName>SCM Administrator</displayName>
<mail>scm-admin@scm-manager.org</mail> <mail>scm-admin@scm-manager.org</mail>
<password>ff8f5c593a01f9fcd3ed48b09a4b013e8d8f3be7</password> <password>$shiro1$SHA-512$8192$$yrNahBVDa4Gz+y5gat4msdjyvjtHlVE+N5nTl4WIDhtBFwhSIib13mKJt1sWmVqgHDWi3VwX7fkdkJ2+WToTbw==</password>
<admin>true</admin> <admin>true</admin>
<type>xml</type> <type>xml</type>
</users> </users>

View File

@@ -35,6 +35,7 @@ package sonia.scm.web;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
@@ -51,13 +52,17 @@ import sonia.scm.repository.HgRepositoryHandler;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryProvider; import sonia.scm.repository.RepositoryProvider;
import sonia.scm.repository.RepositoryRequestListenerUtil; import sonia.scm.repository.RepositoryRequestListenerUtil;
import sonia.scm.security.CipherUtil;
import sonia.scm.util.AssertUtil; import sonia.scm.util.AssertUtil;
import sonia.scm.util.HttpUtil;
import sonia.scm.web.cgi.CGIExecutor; import sonia.scm.web.cgi.CGIExecutor;
import sonia.scm.web.cgi.CGIExecutorFactory; import sonia.scm.web.cgi.CGIExecutorFactory;
import sonia.scm.web.cgi.EnvList; import sonia.scm.web.cgi.EnvList;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import com.sun.jersey.core.util.Base64;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -86,6 +91,9 @@ public class HgCGIServlet extends HttpServlet
/** Field description */ /** Field description */
public static final String ENV_SESSION_PREFIX = "SCM_"; public static final String ENV_SESSION_PREFIX = "SCM_";
/** Field description */
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
/** Field description */ /** Field description */
private static final long serialVersionUID = -3492811300905099810L; private static final long serialVersionUID = -3492811300905099810L;
@@ -183,6 +191,39 @@ public class HgCGIServlet extends HttpServlet
} }
} }
/**
* Method description
*
*
* @param env
* @param request
*/
private void addCredentials(EnvList env, HttpServletRequest request)
{
String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION);
if (!Strings.isNullOrEmpty(authorization))
{
if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC))
{
String encodedUserInfo =
authorization.substring(
HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim();
String userInfo = Base64.base64Decode(encodedUserInfo);
env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo));
}
else
{
logger.warn("unknow authentication scheme used");
}
}
else
{
logger.trace("no authorization header found");
}
}
/** /**
* Method description * Method description
* *
@@ -259,6 +300,7 @@ public class HgCGIServlet extends HttpServlet
executor.getEnvironment().set(ENV_REPOSITORY_NAME, name); executor.getEnvironment().set(ENV_REPOSITORY_NAME, name);
executor.getEnvironment().set(ENV_REPOSITORY_PATH, executor.getEnvironment().set(ENV_REPOSITORY_PATH,
directory.getAbsolutePath()); directory.getAbsolutePath());
// add hook environment // add hook environment
//J- //J-
HgEnvironment.prepareEnvironment( HgEnvironment.prepareEnvironment(
@@ -269,6 +311,9 @@ public class HgCGIServlet extends HttpServlet
); );
//J+ //J+
addCredentials(executor.getEnvironment(), request);
// unused ???
HttpSession session = request.getSession(); HttpSession session = request.getSession();
if (session != null) if (session != null)

View File

@@ -53,7 +53,6 @@ import sonia.scm.store.StoreFactory;
import sonia.scm.upgrade.UpgradeManager; import sonia.scm.upgrade.UpgradeManager;
import sonia.scm.user.UserManager; import sonia.scm.user.UserManager;
import sonia.scm.util.IOUtil; import sonia.scm.util.IOUtil;
import sonia.scm.web.security.AuthenticationManager;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -100,9 +99,6 @@ public class ScmContextListener extends GuiceServletContextListener
// close RepositoryManager // close RepositoryManager
IOUtil.close(globalInjector.getInstance(RepositoryManager.class)); IOUtil.close(globalInjector.getInstance(RepositoryManager.class));
// close Authenticator
IOUtil.close(globalInjector.getInstance(AuthenticationManager.class));
// close GroupManager // close GroupManager
IOUtil.close(globalInjector.getInstance(GroupManager.class)); IOUtil.close(globalInjector.getInstance(GroupManager.class));

View File

@@ -37,9 +37,12 @@ package sonia.scm;
import com.google.inject.name.Names; import com.google.inject.name.Names;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.guice.web.ShiroWebModule; import org.apache.shiro.guice.web.ShiroWebModule;
import sonia.scm.security.ScmRealm; import sonia.scm.security.DefaultRealm;
import static org.apache.shiro.guice.web.ShiroWebModule.ROLES; import static org.apache.shiro.guice.web.ShiroWebModule.ROLES;
@@ -54,6 +57,11 @@ import javax.servlet.ServletContext;
public class ScmSecurityModule extends ShiroWebModule public class ScmSecurityModule extends ShiroWebModule
{ {
/** Field description */
private static final int ITERATIONS = 8192;
//~--- constructors ---------------------------------------------------------
/** /**
* Constructs ... * Constructs ...
* *
@@ -75,9 +83,13 @@ public class ScmSecurityModule extends ShiroWebModule
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
protected void configureShiroWeb() protected void configureShiroWeb()
{ {
bind(PasswordService.class).toInstance(createPasswordService());
// expose password service to global injector
expose(PasswordService.class);
// bind realm // bind realm
bindRealm().to(ScmRealm.class); bindRealm().to(DefaultRealm.class);
// bind constant // bind constant
bindConstant().annotatedWith(Names.named("shiro.loginUrl")).to( bindConstant().annotatedWith(Names.named("shiro.loginUrl")).to(
@@ -86,4 +98,21 @@ public class ScmSecurityModule extends ShiroWebModule
// disable access to mustache resources // disable access to mustache resources
addFilterChain("/**.mustache", config(ROLES, "nobody")); addFilterChain("/**.mustache", config(ROLES, "nobody"));
} }
/**
* Creates a {@link PasswordService} with a smaller size of iteration, because
* large iterations will slow down subversion.
*
* @return instance of {@link PasswordService}
*/
private PasswordService createPasswordService()
{
DefaultPasswordService passwordService = new DefaultPasswordService();
DefaultHashService hashService = new DefaultHashService();
hashService.setHashIterations(ITERATIONS);
passwordService.setHashService(hashService);
return passwordService;
}
} }

View File

@@ -88,13 +88,9 @@ import sonia.scm.resources.ResourceManager;
import sonia.scm.resources.ScriptResourceServlet; import sonia.scm.resources.ScriptResourceServlet;
import sonia.scm.security.CipherHandler; import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil; import sonia.scm.security.CipherUtil;
import sonia.scm.security.ConfigurableLoginAttemptHandler;
import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem; import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.EncryptionHandler;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.MessageDigestEncryptionHandler;
import sonia.scm.security.RepositoryPermissionResolver; import sonia.scm.security.RepositoryPermissionResolver;
import sonia.scm.security.SecuritySystem; import sonia.scm.security.SecuritySystem;
import sonia.scm.store.BlobStoreFactory; import sonia.scm.store.BlobStoreFactory;
@@ -127,8 +123,6 @@ import sonia.scm.web.filter.AutoLoginFilter;
import sonia.scm.web.filter.LoggingFilter; import sonia.scm.web.filter.LoggingFilter;
import sonia.scm.web.security.AdministrationContext; import sonia.scm.web.security.AdministrationContext;
import sonia.scm.web.security.ApiBasicAuthenticationFilter; import sonia.scm.web.security.ApiBasicAuthenticationFilter;
import sonia.scm.web.security.AuthenticationManager;
import sonia.scm.web.security.ChainAuthenticatonManager;
import sonia.scm.web.security.DefaultAdministrationContext; import sonia.scm.web.security.DefaultAdministrationContext;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -269,7 +263,6 @@ public class ScmServletModule extends JerseyServletModule
// note CipherUtil uses an other generator // note CipherUtil uses an other generator
bind(KeyGenerator.class).to(DefaultKeyGenerator.class); bind(KeyGenerator.class).to(DefaultKeyGenerator.class);
bind(CipherHandler.class).toInstance(cu.getCipherHandler()); bind(CipherHandler.class).toInstance(cu.getCipherHandler());
bind(EncryptionHandler.class, MessageDigestEncryptionHandler.class);
bind(FileSystem.class, DefaultFileSystem.class); bind(FileSystem.class, DefaultFileSystem.class);
// bind health check stuff // bind health check stuff
@@ -280,10 +273,8 @@ public class ScmServletModule extends JerseyServletModule
// bind security stuff // bind security stuff
bind(PermissionResolver.class, RepositoryPermissionResolver.class); bind(PermissionResolver.class, RepositoryPermissionResolver.class);
bind(AuthenticationManager.class, ChainAuthenticatonManager.class);
bind(SecuritySystem.class).to(DefaultSecuritySystem.class); bind(SecuritySystem.class).to(DefaultSecuritySystem.class);
bind(AdministrationContext.class, DefaultAdministrationContext.class); bind(AdministrationContext.class, DefaultAdministrationContext.class);
bind(LoginAttemptHandler.class, ConfigurableLoginAttemptHandler.class);
// bind cache // bind cache
bind(CacheManager.class, GuavaCacheManager.class); bind(CacheManager.class, GuavaCacheManager.class);

View File

@@ -38,6 +38,7 @@ package sonia.scm.api.rest.resources;
import com.google.inject.Inject; import com.google.inject.Inject;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.jaxrs.TypeHint;
@@ -47,7 +48,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import sonia.scm.api.rest.RestActionResult; import sonia.scm.api.rest.RestActionResult;
import sonia.scm.security.EncryptionHandler; import sonia.scm.security.Role;
import sonia.scm.security.ScmSecurityException; import sonia.scm.security.ScmSecurityException;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserException; import sonia.scm.user.UserException;
@@ -65,7 +66,6 @@ import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import sonia.scm.security.Role;
/** /**
* *
@@ -88,14 +88,13 @@ public class ChangePasswordResource
* *
* @param userManager * @param userManager
* @param encryptionHandler * @param encryptionHandler
* @param securityContextProvider
*/ */
@Inject @Inject
public ChangePasswordResource(UserManager userManager, public ChangePasswordResource(UserManager userManager,
EncryptionHandler encryptionHandler) PasswordService encryptionHandler)
{ {
this.userManager = userManager; this.userManager = userManager;
this.encryptionHandler = encryptionHandler; this.passwordService = encryptionHandler;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -155,9 +154,9 @@ public class ChangePasswordResource
{ {
User dbUser = userManager.get(currentUser.getName()); User dbUser = userManager.get(currentUser.getName());
if (encryptionHandler.encrypt(oldPassword).equals(dbUser.getPassword())) if (passwordService.passwordsMatch(oldPassword, dbUser.getPassword()))
{ {
dbUser.setPassword(encryptionHandler.encrypt(newPassword)); dbUser.setPassword(passwordService.encryptPassword(newPassword));
userManager.modify(dbUser); userManager.modify(dbUser);
response = Response.ok(new RestActionResult(true)).build(); response = Response.ok(new RestActionResult(true)).build();
} }
@@ -183,8 +182,8 @@ public class ChangePasswordResource
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private EncryptionHandler encryptionHandler; private final PasswordService passwordService;
/** Field description */ /** Field description */
private UserManager userManager; private final UserManager userManager;
} }

View File

@@ -39,11 +39,11 @@ import com.google.inject.Inject;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.credential.PasswordService;
import org.codehaus.enunciate.jaxrs.TypeHint; import org.codehaus.enunciate.jaxrs.TypeHint;
import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle; import org.codehaus.enunciate.modules.jersey.ExternallyManagedLifecycle;
import sonia.scm.security.EncryptionHandler;
import sonia.scm.security.Role; import sonia.scm.security.Role;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.user.UserException; import sonia.scm.user.UserException;
@@ -95,15 +95,13 @@ public class UserResource extends AbstractManagerResource<User, UserException>
* *
* *
* @param userManager * @param userManager
* @param encryptionHandler * @param passwordService
* @param securityContextProvider
*/ */
@Inject @Inject
public UserResource(UserManager userManager, public UserResource(UserManager userManager, PasswordService passwordService)
EncryptionHandler encryptionHandler)
{ {
super(userManager); super(userManager);
this.encryptionHandler = encryptionHandler; this.passwordService = passwordService;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -386,12 +384,12 @@ public class UserResource extends AbstractManagerResource<User, UserException>
if (Util.isNotEmpty(password)) if (Util.isNotEmpty(password))
{ {
user.setPassword(encryptionHandler.encrypt(password)); user.setPassword(passwordService.encryptPassword(password));
} }
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** Field description */
private EncryptionHandler encryptionHandler; private PasswordService passwordService;
} }

View File

@@ -1,331 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.web.security.AuthenticationResult;
//~--- JDK imports ------------------------------------------------------------
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class ConfigurableLoginAttemptHandler implements LoginAttemptHandler
{
/**
* the logger for ConfigurableLoginAttemptHandler
*/
private static final Logger logger =
LoggerFactory.getLogger(ConfigurableLoginAttemptHandler.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param configuration
*/
@Inject
public ConfigurableLoginAttemptHandler(ScmConfiguration configuration)
{
this.configuration = configuration;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param token
*
* @throws AuthenticationException
*/
@Override
public void beforeAuthentication(AuthenticationToken token)
throws AuthenticationException
{
if (isEnabled())
{
handleBeforeAuthentication(token);
}
else
{
logger.trace("LoginAttemptHandler is disabled");
}
}
/**
* Method description
*
*
* @param token
* @param result
*
* @throws AuthenticationException
*/
@Override
public void onSuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException
{
if (isEnabled())
{
handleOnSuccessfulAuthentication(token);
}
else
{
logger.trace("LoginAttemptHandler is disabled");
}
}
/**
* Method description
*
*
* @param token
* @param result
*
* @throws AuthenticationException
*/
@Override
public void onUnsuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result)
throws AuthenticationException
{
if (isEnabled())
{
handleOnUnsuccessfulAuthentication(token);
}
else
{
logger.trace("LoginAttemptHandler is disabled");
}
}
/**
* Method description
*
*
* @param token
*/
private void handleBeforeAuthentication(AuthenticationToken token)
{
LoginAttempt attempt = getAttempt(token);
long time = System.currentTimeMillis() - attempt.lastAttempt;
if (time > getLoginAttemptLimitTimeout())
{
logger.debug("reset login attempts for {}, because of time",
token.getPrincipal());
attempt.reset();
}
else if (attempt.counter >= configuration.getLoginAttemptLimit())
{
logger.warn("account {} is temporary locked, because of {}",
token.getPrincipal(), attempt);
attempt.increase();
throw new ExcessiveAttemptsException("account is temporary locked");
}
}
/**
* Method description
*
*
* @param token
* @param result
*
* @throws AuthenticationException
*/
private void handleOnSuccessfulAuthentication(AuthenticationToken token)
throws AuthenticationException
{
logger.debug("reset login attempts for {}, because of successful login",
token.getPrincipal());
getAttempt(token).reset();
}
/**
* Method description
*
*
* @param token
* @param result
*
* @throws AuthenticationException
*/
private void handleOnUnsuccessfulAuthentication(AuthenticationToken token)
throws AuthenticationException
{
logger.debug("increase failed login attempts for {}", token.getPrincipal());
LoginAttempt attempt = getAttempt(token);
attempt.increase();
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param token
*
* @return
*/
private LoginAttempt getAttempt(AuthenticationToken token)
{
LoginAttempt freshAttempt = new LoginAttempt();
LoginAttempt attempt = attempts.putIfAbsent(token.getPrincipal(),
freshAttempt);
if (attempt == null)
{
attempt = freshAttempt;
}
return attempt;
}
/**
* Method description
*
*
* @return
*/
private long getLoginAttemptLimitTimeout()
{
return TimeUnit.SECONDS.toMillis(
configuration.getLoginAttemptLimitTimeout());
}
/**
* Method description
*
*
* @return
*/
private boolean isEnabled()
{
return (configuration.getLoginAttemptLimit() > 0)
&& (configuration.getLoginAttemptLimitTimeout() > 0l);
}
//~--- inner classes --------------------------------------------------------
/**
* Login attempt
*/
private static class LoginAttempt
{
/**
* Method description
*
*
* @return
*/
@Override
public String toString()
{
//J-
return Objects.toStringHelper(this)
.add("counter", counter)
.add("lastAttempt", lastAttempt)
.toString();
//J+
}
/**
* Method description
*
*/
synchronized void increase()
{
counter++;
lastAttempt = System.currentTimeMillis();
}
/**
* Method description
*
*/
synchronized void reset()
{
lastAttempt = -1l;
counter = 0;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private int counter = 0;
/** Field description */
private long lastAttempt = -1l;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final ConcurrentMap<Object, LoginAttempt> attempts =
new ConcurrentHashMap<Object, LoginAttempt>();
/** Field description */
private final ScmConfiguration configuration;
}

View File

@@ -0,0 +1,228 @@
/**
* Copyright (c) 2014, 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.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSet.Builder;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.PasswordMatcher;
import org.apache.shiro.authc.credential.PasswordService;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------
import javax.inject.Inject;
import javax.inject.Singleton;
/**
*
* @author Sebastian Sdorra
*
* @since 2.0.0
*/
@Singleton
public class DefaultRealm extends AuthorizingRealm
{
/** Field description */
@VisibleForTesting
static final String REALM = "DefaultRealm";
/**
* the logger for DefaultRealm
*/
private static final Logger logger =
LoggerFactory.getLogger(DefaultRealm.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param service
* @param collector
* @param userDAO
* @param groupDAO
*/
@Inject
public DefaultRealm(PasswordService service,
AuthorizationCollector collector, UserDAO userDAO, GroupDAO groupDAO)
{
this.collector = collector;
this.userDAO = userDAO;
this.groupDAO = groupDAO;
PasswordMatcher matcher = new PasswordMatcher();
matcher.setPasswordService(service);
setCredentialsMatcher(matcher);
setAuthenticationTokenClass(UsernamePasswordToken.class);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param token
*
* @return
*
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token)
throws AuthenticationException
{
checkArgument(token instanceof UsernamePasswordToken, "%s is required",
UsernamePasswordToken.class);
UsernamePasswordToken upt = (UsernamePasswordToken) token;
String principal = upt.getUsername();
checkArgument(!Strings.isNullOrEmpty(principal), "username is required");
logger.debug("try to authenticate {}", principal);
User user = userDAO.get(principal);
if (user == null)
{
//J-
throw new UnknownAccountException(
String.format("unknown account %s", principal)
);
//J+
}
if (!user.isActive())
{
//J-
throw new DisabledAccountException(
String.format("account %s is disabled", principal)
);
//J+
}
SimplePrincipalCollection collection = new SimplePrincipalCollection();
collection.add(principal, REALM);
collection.add(user, REALM);
collection.add(collectGroups(principal), REALM);
return new SimpleAuthenticationInfo(collection, user.getPassword());
}
/**
* Method description
*
*
* @param principals
*
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals)
{
return collector.collect(principals);
}
/**
* Method description
*
*
* @param principal
*
* @return
*/
private GroupNames collectGroups(String principal)
{
Builder<String> builder = ImmutableSet.builder();
builder.add(GroupNames.AUTHENTICATED);
for (Group group : groupDAO.getAll())
{
if (group.isMember(principal))
{
builder.add(group.getName());
}
}
GroupNames groups = new GroupNames(builder.build());
logger.debug("collected following groups for principal {}: {}", principal,
groups);
return groups;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final AuthorizationCollector collector;
/** Field description */
private final GroupDAO groupDAO;
/** Field description */
private final UserDAO userDAO;
}

View File

@@ -1,566 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Joiner;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.HandlerEventType;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupNames;
import sonia.scm.repository.RepositoryManager;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserEventHack;
import sonia.scm.user.UserException;
import sonia.scm.user.UserManager;
import sonia.scm.util.Util;
import sonia.scm.web.security.AuthenticationManager;
import sonia.scm.web.security.AuthenticationResult;
import sonia.scm.web.security.AuthenticationState;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Collection;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class ScmRealm extends AuthorizingRealm
{
/** Field description */
public static final String NAME = "scm";
/** Field description */
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
/**
* the logger for ScmRealm
*/
private static final Logger logger = LoggerFactory.getLogger(ScmRealm.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
* @param configuration
* @param loginAttemptHandler
* @param collector
* @param userManager
* @param groupManager
* @param userDAO
* @param authenticator
* @param manager
* @param requestProvider
* @param responseProvider
*/
@Inject
public ScmRealm(ScmConfiguration configuration,
LoginAttemptHandler loginAttemptHandler, AuthorizationCollector collector,
UserManager userManager, GroupManager groupManager, UserDAO userDAO,
AuthenticationManager authenticator, RepositoryManager manager,
Provider<HttpServletRequest> requestProvider,
Provider<HttpServletResponse> responseProvider)
{
this.configuration = configuration;
this.loginAttemptHandler = loginAttemptHandler;
this.collector = collector;
this.userManager = userManager;
this.groupManager = groupManager;
this.userDAO = userDAO;
this.authenticator = authenticator;
this.requestProvider = requestProvider;
this.responseProvider = responseProvider;
// set token class
setAuthenticationTokenClass(UsernamePasswordToken.class);
// use own custom caching
setCachingEnabled(false);
setAuthenticationCachingEnabled(false);
setAuthorizationCachingEnabled(false);
// set components
setPermissionResolver(new RepositoryPermissionResolver());
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
*
* @param authToken
*
* @return
*
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken authToken)
throws AuthenticationException
{
if (!(authToken instanceof UsernamePasswordToken))
{
throw new UnsupportedTokenException("ScmAuthenticationToken is required");
}
loginAttemptHandler.beforeAuthentication(authToken);
UsernamePasswordToken token = (UsernamePasswordToken) authToken;
AuthenticationInfo info = null;
AuthenticationResult result =
authenticator.authenticate(requestProvider.get(), responseProvider.get(),
token.getUsername(), new String(token.getPassword()));
if ((result != null) && (AuthenticationState.SUCCESS == result.getState()))
{
loginAttemptHandler.onSuccessfulAuthentication(authToken, result);
info = createAuthenticationInfo(token, result);
}
else if ((result != null)
&& (AuthenticationState.NOT_FOUND == result.getState()))
{
throw new UnknownAccountException(
"unknown account ".concat(token.getUsername()));
}
else
{
loginAttemptHandler.onUnsuccessfulAuthentication(authToken, result);
throw new AccountException("authentication failed");
}
return info;
}
/**
* Method description
*
*
* @param principals
*
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principals)
{
return collector.collect(principals);
}
/**
* Method description
*
*
* @param request
* @param password
* @param ar
*
* @return
*/
private Set<String> authenticate(HttpServletRequest request, String password,
AuthenticationResult ar)
{
Set<String> groupSet = null;
User user = ar.getUser();
try
{
groupSet = createGroupSet(ar);
// check for admin user
checkForAuthenticatedAdmin(user, groupSet);
// store user
User dbUser = userDAO.get(user.getName());
if (dbUser != null)
{
checkDBForAdmin(user, dbUser);
checkDBForActive(user, dbUser);
}
// create new user
else if (user.isValid())
{
user.setCreationDate(System.currentTimeMillis());
// TODO find a better way
UserEventHack.fireEvent(userManager, HandlerEventType.BEFORE_CREATE,
user);
userDAO.add(user);
UserEventHack.fireEvent(userManager, HandlerEventType.CREATE, user);
}
else if (logger.isErrorEnabled())
{
logger.error("could not create user {}, beacause it is not valid",
user.getName());
}
if (user.isActive())
{
if (logger.isDebugEnabled())
{
logGroups(user, groupSet);
}
// store encrypted credentials in session
String credentials = user.getName();
if (Util.isNotEmpty(password))
{
credentials = credentials.concat(":").concat(password);
}
credentials = CipherUtil.getInstance().encode(credentials);
request.getSession(true).setAttribute(SCM_CREDENTIALS, credentials);
}
else
{
String msg = "user ".concat(user.getName()).concat(" is deactivated");
if (logger.isWarnEnabled())
{
logger.warn(msg);
}
throw new DisabledAccountException(msg);
}
}
catch (Exception ex)
{
logger.error("authentication failed", ex);
throw new AuthenticationException("authentication failed", ex);
}
return groupSet;
}
/**
* Method description
*
*
* @param user
* @param dbUser
*/
private void checkDBForActive(User user, User dbUser)
{
// user is deactivated by database
if (!dbUser.isActive())
{
if (logger.isDebugEnabled())
{
logger.debug("user {} is marked as deactivated by local database",
user.getName());
}
user.setActive(false);
}
}
/**
* Method description
*
*
* @param user
* @param dbUser
*
* @throws IOException
* @throws UserException
*/
private void checkDBForAdmin(User user, User dbUser)
throws UserException, IOException
{
// if database user is an admin, set admin for the current user
if (dbUser.isAdmin())
{
if (logger.isDebugEnabled())
{
logger.debug("user {} of type {} is marked as admin by local database",
user.getName(), user.getType());
}
user.setAdmin(true);
}
// modify existing user, copy properties except password and admin
if (user.copyProperties(dbUser, false))
{
user.setLastModified(System.currentTimeMillis());
UserEventHack.fireEvent(userManager, HandlerEventType.BEFORE_MODIFY, user);
userDAO.modify(user);
UserEventHack.fireEvent(userManager, HandlerEventType.MODIFY, user);
}
}
/**
* Method description
*
*
* @param user
* @param groupSet
*/
private void checkForAuthenticatedAdmin(User user, Set<String> groupSet)
{
if (!user.isAdmin())
{
user.setAdmin(isAdmin(user, groupSet));
if (logger.isDebugEnabled() && user.isAdmin())
{
logger.debug("user {} is marked as admin by configuration",
user.getName());
}
}
else if (logger.isDebugEnabled())
{
logger.debug("authenticator {} marked user {} as admin", user.getType(),
user.getName());
}
}
/**
* Method description
*
*
* @param token
* @param result
*
* @return
*/
private AuthenticationInfo createAuthenticationInfo(
UsernamePasswordToken token, AuthenticationResult result)
{
User user = result.getUser();
Collection<String> groups = authenticate(requestProvider.get(),
new String(token.getPassword()), result);
SimplePrincipalCollection collection = new SimplePrincipalCollection();
/*
* the first (primary) principal should be a unique identifier
*/
collection.add(user.getId(), NAME);
collection.add(user, NAME);
collection.add(new GroupNames(groups), NAME);
return new SimpleAuthenticationInfo(collection, token.getPassword());
}
/**
* Method description
*
*
* @param ar
*
* @return
*/
private Set<String> createGroupSet(AuthenticationResult ar)
{
Set<String> groupSet = Sets.newHashSet();
// add group for all authenticated users
groupSet.add(GroupNames.AUTHENTICATED);
// load external groups
Collection<String> extGroups = ar.getGroups();
if (extGroups != null)
{
groupSet.addAll(extGroups);
}
// load internal groups
loadGroups(ar.getUser(), groupSet);
return groupSet;
}
/**
* Method description
*
*
*
* @param user
* @param groupSet
*/
private void loadGroups(User user, Set<String> groupSet)
{
Collection<Group> groupCollection =
groupManager.getGroupsForMember(user.getName());
if (groupCollection != null)
{
for (Group group : groupCollection)
{
groupSet.add(group.getName());
}
}
}
/**
* Method description
*
*
* @param user
* @param groups
*/
private void logGroups(User user, Set<String> groups)
{
StringBuilder msg = new StringBuilder("user ");
msg.append(user.getName());
if (Util.isNotEmpty(groups))
{
msg.append(" is member of ");
Joiner.on(", ").appendTo(msg, groups);
}
else
{
msg.append(" is not a member of a group");
}
logger.debug(msg.toString());
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
*
*
* @param user
* @param groups
* @return
*/
private boolean isAdmin(User user, Collection<String> groups)
{
boolean result = false;
Set<String> adminUsers = configuration.getAdminUsers();
if (adminUsers != null)
{
result = adminUsers.contains(user.getName());
}
if (!result)
{
Set<String> adminGroups = configuration.getAdminGroups();
if (adminGroups != null)
{
result = Util.containsOne(adminGroups, groups);
}
}
return result;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private final AuthenticationManager authenticator;
/** Field description */
private final AuthorizationCollector collector;
/** Field description */
private final ScmConfiguration configuration;
/** Field description */
private final GroupManager groupManager;
/** Field description */
private final LoginAttemptHandler loginAttemptHandler;
/** Field description */
private final Provider<HttpServletRequest> requestProvider;
/** Field description */
private final Provider<HttpServletResponse> responseProvider;
/** Field description */
private final UserDAO userDAO;
/** Field description */
private final UserManager userManager;
}

View File

@@ -1,398 +0,0 @@
/**
* 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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.EncryptionHandler;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.util.AssertUtil;
import sonia.scm.util.IOUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
@Singleton
public class ChainAuthenticatonManager extends AbstractAuthenticationManager
{
/** Field description */
public static final String CACHE_NAME = "sonia.cache.auth";
/** the logger for ChainAuthenticatonManager */
private static final Logger logger =
LoggerFactory.getLogger(ChainAuthenticatonManager.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param configuration
* @param userManager
* @param authenticationHandlerSet
* @param encryptionHandler
* @param cacheManager
*/
@Inject
public ChainAuthenticatonManager(ScmConfiguration configuration,
UserManager userManager,
Set<AuthenticationHandler> authenticationHandlerSet,
EncryptionHandler encryptionHandler, CacheManager cacheManager)
{
AssertUtil.assertIsNotEmpty(authenticationHandlerSet);
AssertUtil.assertIsNotNull(cacheManager);
this.configuration = configuration;
this.authenticationHandlers = sort(userManager, authenticationHandlerSet);
this.encryptionHandler = encryptionHandler;
this.cache = cacheManager.getCache(CACHE_NAME);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
* @param username
* @param password
*
* @return
*/
@Override
public AuthenticationResult authenticate(HttpServletRequest request,
HttpServletResponse response, String username, String password)
{
AssertUtil.assertIsNotEmpty(username);
AssertUtil.assertIsNotEmpty(password);
String encryptedPassword = encryptionHandler.encrypt(password);
AuthenticationResult ar = getCached(username, encryptedPassword);
if (ar == null)
{
if (logger.isTraceEnabled())
{
logger.trace("no authentication result for user {} found in cache",
username);
}
ar = doAuthentication(request, response, username, password);
if ((ar != null) && ar.isCacheable())
{
cache.put(username,
new AuthenticationCacheValue(ar, encryptedPassword));
}
}
else if (logger.isDebugEnabled())
{
logger.debug("authenticate {} via cache", username);
}
return ar;
}
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException
{
for (AuthenticationHandler authenticator : authenticationHandlers)
{
if (logger.isTraceEnabled())
{
logger.trace("close authenticator {}", authenticator.getClass());
}
IOUtil.close(authenticator);
}
}
/**
* Method description
*
*
* @param context
*/
@Override
public void init(SCMContextProvider context)
{
for (AuthenticationHandler authenticator : authenticationHandlers)
{
if (logger.isTraceEnabled())
{
logger.trace("initialize authenticator {}", authenticator.getClass());
}
authenticator.init(context);
}
}
/**
* Method description
*
*
* @param result
*
* @return
*/
boolean stopChain(AuthenticationResult result)
{
return (result != null) && (result.getState() != null)
&& (result.getState().isSuccessfully()
|| ((result.getState() == AuthenticationState.FAILED)
&&!configuration.isSkipFailedAuthenticators()));
}
/**
* Method description
*
*
* @param request
* @param response
* @param username
* @param password
*
* @return
*/
private AuthenticationResult doAuthentication(HttpServletRequest request,
HttpServletResponse response, String username, String password)
{
AuthenticationResult ar = null;
if (logger.isTraceEnabled())
{
logger.trace("start authentication chain for user {}", username);
}
for (AuthenticationHandler authenticator : authenticationHandlers)
{
if (logger.isTraceEnabled())
{
logger.trace("check authenticator {} for user {}",
authenticator.getClass(), username);
}
try
{
AuthenticationResult result = authenticator.authenticate(request,
response, username, password);
if (logger.isDebugEnabled())
{
logger.debug("authenticator {} ends with result, {}",
authenticator.getClass().getName(), result);
}
if (stopChain(result))
{
if (result.getState().isSuccessfully() && (result.getUser() != null))
{
User user = result.getUser();
user.setType(authenticator.getType());
ar = result;
// notify authentication listeners
fireAuthenticationEvent(user);
}
break;
}
}
catch (Exception ex)
{
logger.error(
"error durring authentication process of ".concat(
authenticator.getClass().getName()), ex);
}
}
return ar;
}
/**
* Method description
*
*
* @param userManager
* @param authenticationHandlerSet
*
* @return
*/
@VisibleForTesting
private List<AuthenticationHandler> sort(UserManager userManager,
Set<AuthenticationHandler> authenticationHandlerSet)
{
List<AuthenticationHandler> handlers =
Lists.newArrayListWithCapacity(authenticationHandlerSet.size());
String first = Strings.nullToEmpty(userManager.getDefaultType());
for (AuthenticationHandler handler : authenticationHandlerSet)
{
if (first.equals(handler.getType()))
{
handlers.add(0, handler);
}
else
{
handlers.add(handler);
}
}
return handlers;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @param username
* @param encryptedPassword
*
* @return
*/
private AuthenticationResult getCached(String username,
String encryptedPassword)
{
AuthenticationResult result = null;
AuthenticationCacheValue value = cache.get(username);
if (value != null)
{
String cachedPassword = value.password;
if (cachedPassword.equals(encryptedPassword))
{
result = value.authenticationResult;
}
}
return result;
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 2011-01-15
* @author Sebastian Sdorra
*/
private static class AuthenticationCacheValue implements Serializable
{
/** Field description */
private static final long serialVersionUID = 2201116145941277549L;
//~--- constructors -------------------------------------------------------
/**
* Constructs ...
*
*
*
* @param ar
* @param password
*/
public AuthenticationCacheValue(AuthenticationResult ar, String password)
{
this.authenticationResult =
new AuthenticationResult(ar.getUser().clone(), ar.getGroups(),
ar.getState());
this.password = password;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private AuthenticationResult authenticationResult;
/** Field description */
private String password;
}
//~--- fields ---------------------------------------------------------------
/** authentication handlers */
private final List<AuthenticationHandler> authenticationHandlers;
/** authentication cache */
private final Cache<String, AuthenticationCacheValue> cache;
/** Field description */
private final ScmConfiguration configuration;
/** encryption handler */
private final EncryptionHandler encryptionHandler;
}

View File

@@ -53,7 +53,6 @@ import org.slf4j.LoggerFactory;
import sonia.scm.SCMContext; import sonia.scm.SCMContext;
import sonia.scm.group.GroupNames; import sonia.scm.group.GroupNames;
import sonia.scm.security.Role; import sonia.scm.security.Role;
import sonia.scm.security.ScmRealm;
import sonia.scm.user.User; import sonia.scm.user.User;
import sonia.scm.util.AssertUtil; import sonia.scm.util.AssertUtil;
@@ -75,6 +74,9 @@ public class DefaultAdministrationContext implements AdministrationContext
public static final String SYSTEM_ACCOUNT = public static final String SYSTEM_ACCOUNT =
"/sonia/scm/web/security/system-account.xml"; "/sonia/scm/web/security/system-account.xml";
/** Field description */
private static final String REALM = "AdminRealm";
/** the logger for DefaultAdministrationContext */ /** the logger for DefaultAdministrationContext */
private static final Logger logger = private static final Logger logger =
LoggerFactory.getLogger(DefaultAdministrationContext.class); LoggerFactory.getLogger(DefaultAdministrationContext.class);
@@ -169,9 +171,9 @@ public class DefaultAdministrationContext implements AdministrationContext
{ {
SimplePrincipalCollection collection = new SimplePrincipalCollection(); SimplePrincipalCollection collection = new SimplePrincipalCollection();
collection.add(adminUser.getId(), ScmRealm.NAME); collection.add(adminUser.getId(), REALM);
collection.add(adminUser, ScmRealm.NAME); collection.add(adminUser, REALM);
collection.add(new GroupNames(), ScmRealm.NAME); collection.add(new GroupNames(), REALM);
return collection; return collection;
} }

View File

@@ -1,230 +0,0 @@
/**
* 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 com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.SCMContextProvider;
import sonia.scm.security.EncryptionHandler;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sonia.scm.plugin.Extension;
/**
*
* @author Sebastian Sdorra
*/
@Extension
@Singleton
public class DefaultAuthenticationHandler implements AuthenticationHandler
{
/** Field description */
public static final String NAME_DIRECTORY = "users";
/** the logger for XmlAuthenticationHandler */
private static final Logger logger =
LoggerFactory.getLogger(DefaultAuthenticationHandler.class);
//~--- constructors ---------------------------------------------------------
/**
* Constructs ...
*
*
* @param userManager
* @param encryptionHandler
*/
@Inject
public DefaultAuthenticationHandler(UserManager userManager,
EncryptionHandler encryptionHandler)
{
this.userManager = userManager;
this.encryptionHandler = encryptionHandler;
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
* @param username
* @param password
*
* @return
*/
@Override
public AuthenticationResult authenticate(HttpServletRequest request,
HttpServletResponse response, String username, String password)
{
AuthenticationResult result = null;
User user = userManager.get(username);
if (user != null)
{
if (userManager.getDefaultType().equals(user.getType()))
{
result = authenticate(user, username, password);
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("{} is not an {} user", username,
userManager.getDefaultType());
}
result = AuthenticationResult.NOT_FOUND;
}
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("could not find user {}", username);
}
result = AuthenticationResult.NOT_FOUND;
}
return result;
}
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException
{
// do nothing
}
/**
* Method description
*
*
* @param provider
*/
@Override
public void init(SCMContextProvider provider)
{
// do nothing
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getType()
{
return userManager.getDefaultType();
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param user
* @param username
* @param password
*
* @return
*/
private AuthenticationResult authenticate(User user, String username,
String password)
{
AuthenticationResult result = null;
String encryptedPassword = encryptionHandler.encrypt(password);
if (!encryptedPassword.equalsIgnoreCase(user.getPassword()))
{
user = null;
if (logger.isDebugEnabled())
{
logger.debug("password for user {} is wrong", username);
}
result = AuthenticationResult.FAILED;
}
else
{
if (logger.isDebugEnabled())
{
logger.debug("user {} successfully prepared for login", username);
}
user.setPassword(null);
result = new AuthenticationResult(user, AuthenticationState.SUCCESS);
}
return result;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private EncryptionHandler encryptionHandler;
/** Field description */
private UserManager userManager;
}

View File

@@ -50,6 +50,7 @@ import static sonia.scm.it.IntegrationTestUtil.*;
import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse; import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource; import com.sun.jersey.api.client.WebResource;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
@@ -126,9 +127,8 @@ public class DeactivatedUserITCase
Client client = createClient(); Client client = createClient();
ClientResponse response = authenticate(client, slarti.getName(), ClientResponse response = authenticate(client, slarti.getName(),
"slart123"); "slart123");
assertNotNull(response); assertNotNull(response);
assertEquals(401, response.getStatus()); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus());
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------

View File

@@ -1,139 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.junit.Test;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.web.security.AuthenticationResult;
import sonia.scm.web.security.AuthenticationState;
//~--- JDK imports ------------------------------------------------------------
import java.util.concurrent.TimeUnit;
/**
*
* @author Sebastian Sdorra
*/
public class ConfigurableLoginAttemptHandlerTest
{
/**
* Method description
*
*/
@Test(expected = ExcessiveAttemptsException.class)
public void testLoginAttemptLimitReached()
{
LoginAttemptHandler handler = createHandler(2, 2);
UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo");
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
}
/**
* Method description
*
*
* @throws InterruptedException
*/
@Test
public void testLoginAttemptLimitTimeout() throws InterruptedException
{
LoginAttemptHandler handler = createHandler(2, 1);
UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo");
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
Thread.currentThread().sleep(TimeUnit.MILLISECONDS.toMillis(1200l));
handler.beforeAuthentication(token);
}
/**
* Method description
*
*
* @throws InterruptedException
*/
@Test
public void testLoginAttemptResetOnSuccess() throws InterruptedException
{
LoginAttemptHandler handler = createHandler(2, 1);
UsernamePasswordToken token = new UsernamePasswordToken("hansolo", "hobbo");
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.onSuccessfulAuthentication(token,
new AuthenticationResult(AuthenticationState.SUCCESS));
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
handler.beforeAuthentication(token);
handler.onUnsuccessfulAuthentication(token, AuthenticationResult.FAILED);
}
/**
* Method description
*
*
* @param loginAttemptLimit
* @param loginAttemptLimitTimeout
*
* @return
*/
private LoginAttemptHandler createHandler(int loginAttemptLimit,
long loginAttemptLimitTimeout)
{
ScmConfiguration configuration = new ScmConfiguration();
configuration.setLoginAttemptLimit(loginAttemptLimit);
configuration.setLoginAttemptLimitTimeout(loginAttemptLimitTimeout);
return new ConfigurableLoginAttemptHandler(configuration);
}
}

View File

@@ -0,0 +1,308 @@
/**
* Copyright (c) 2014, 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.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.collect.Lists;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import sonia.scm.group.Group;
import sonia.scm.group.GroupDAO;
import sonia.scm.group.GroupNames;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserTestData;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
public class DefaultRealmTest
{
/**
* Method description
*
*/
@Test(expected = DisabledAccountException.class)
public void testDisabledAccount()
{
User user = UserTestData.createMarvin();
user.setActive(false);
UsernamePasswordToken token = daoUser(user, "secret");
realm.getAuthenticationInfo(token);
}
/**
* Method description
*
*/
@Test
public void testGetAuthorizationInfo()
{
SimplePrincipalCollection col = new SimplePrincipalCollection();
realm.doGetAuthorizationInfo(col);
verify(collector, times(1)).collect(col);
}
/**
* Method description
*
*/
@Test
public void testGroupCollection()
{
User user = UserTestData.createTrillian();
//J-
List<Group> groups = Lists.newArrayList(
new Group(DefaultRealm.REALM, "scm", user.getName()),
new Group(DefaultRealm.REALM, "developers", "perfect")
);
//J+
when(groupDAO.getAll()).thenReturn(groups);
UsernamePasswordToken token = daoUser(user, "secret");
AuthenticationInfo info = realm.getAuthenticationInfo(token);
GroupNames groupNames = info.getPrincipals().oneByType(GroupNames.class);
assertNotNull(groupNames);
assertThat(groupNames.getCollection(), hasSize(2));
assertThat(groupNames, hasItems("scm", GroupNames.AUTHENTICATED));
}
/**
* Method description
*
*/
@Test
public void testSimpleAuthentication()
{
User user = UserTestData.createTrillian();
UsernamePasswordToken token = daoUser(user, "secret");
AuthenticationInfo info = realm.getAuthenticationInfo(token);
assertNotNull(info);
PrincipalCollection collection = info.getPrincipals();
assertEquals(token.getUsername(), collection.getPrimaryPrincipal());
assertThat(collection.getRealmNames(), hasSize(1));
assertThat(collection.getRealmNames(), hasItem(DefaultRealm.REALM));
assertEquals(user, collection.oneByType(User.class));
GroupNames groups = collection.oneByType(GroupNames.class);
assertNotNull(groups);
assertThat(groups.getCollection(), hasSize(1));
assertThat(groups.getCollection(), hasItem(GroupNames.AUTHENTICATED));
}
/**
* Method description
*
*/
@Test(expected = UnknownAccountException.class)
public void testUnknownAccount()
{
realm.getAuthenticationInfo(new UsernamePasswordToken("tricia", "secret"));
}
/**
* Method description
*
*/
@Test(expected = IllegalArgumentException.class)
public void testWithoutUsername()
{
realm.getAuthenticationInfo(new UsernamePasswordToken(null, "secret"));
}
/**
* Method description
*
*/
@Test(expected = IncorrectCredentialsException.class)
public void testWrongCredentials()
{
UsernamePasswordToken token = daoUser(UserTestData.createDent(), "secret");
token.setPassword("secret123".toCharArray());
realm.getAuthenticationInfo(token);
}
/**
* Method description
*
*/
@Test(expected = IllegalArgumentException.class)
public void testWrongToken()
{
realm.getAuthenticationInfo(new OtherAuthenticationToken());
}
//~--- set methods ----------------------------------------------------------
/**
* Method description
*
*/
@Before
public void setUp()
{
service = new DefaultPasswordService();
DefaultHashService hashService = new DefaultHashService();
// use a small number of iterations for faster test execution
hashService.setHashIterations(512);
service.setHashService(hashService);
realm = new DefaultRealm(service, collector, userDAO, groupDAO);
}
//~--- methods --------------------------------------------------------------
/**
* Method description
*
*
* @param user
* @param password
*
* @return
*/
private UsernamePasswordToken daoUser(User user, String password)
{
user.setPassword(service.encryptPassword(password));
when(userDAO.get(user.getName())).thenReturn(user);
return new UsernamePasswordToken(user.getName(), password);
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 14/12/13
* @author Enter your name here...
*/
private static class OtherAuthenticationToken implements AuthenticationToken
{
/** Field description */
private static final long serialVersionUID = 8891352342377018022L;
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public Object getCredentials()
{
throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates.
}
/**
* Method description
*
*
* @return
*/
@Override
public Object getPrincipal()
{
throw new UnsupportedOperationException("Not supported yet."); // To change body of generated methods, choose Tools | Templates.
}
}
//~--- fields ---------------------------------------------------------------
/** Field description */
@Mock
private AuthorizationCollector collector;
/** Field description */
@Mock
private GroupDAO groupDAO;
/** Field description */
private DefaultRealm realm;
/** Field description */
private DefaultPasswordService service;
/** Field description */
@Mock
private UserDAO userDAO;
}

View File

@@ -1,627 +0,0 @@
/**
* 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.security;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Provider;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.subject.PrincipalCollection;
import org.junit.Test;
import org.mockito.Mockito;
import sonia.scm.cache.CacheManager;
import sonia.scm.cache.MapCacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.group.Group;
import sonia.scm.group.GroupManager;
import sonia.scm.group.GroupNames;
import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryDAO;
import sonia.scm.repository.RepositoryTestData;
import sonia.scm.user.User;
import sonia.scm.user.UserDAO;
import sonia.scm.user.UserManager;
import sonia.scm.user.UserTestData;
import sonia.scm.web.security.AuthenticationManager;
import sonia.scm.web.security.AuthenticationResult;
import sonia.scm.web.security.AuthenticationState;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
*
* @author Sebastian Sdorra
*/
public class ScmRealmTest
{
/**
* Method description
*
*/
@Test(expected = UnknownAccountException.class)
public void testAuthenticationWithUknownUser()
{
User trillian = createSampleUser();
ScmRealm realm = createRealm(trillian);
realm.getAuthenticationInfo(token("marvin", trillian.getPassword()));
}
/**
* Method description
*
*/
@Test
public void testAuthorizationAdminPermissions()
{
User trillian = createSampleUser();
trillian.setAdmin(true);
AuthorizationInfo ai = authorizationInfo(trillian);
Collection<Permission> permissions = ai.getObjectPermissions();
assertNotNull(permissions);
assertFalse(permissions.isEmpty());
assertEquals(1, permissions.size());
//J-
assertTrue(
permissions.contains(new RepositoryPermission("*", PermissionType.OWNER))
);
//J+
}
/**
* Method description
*
*/
@Test
public void testAuthorizationAdminRoles()
{
User trillian = createSampleUser();
trillian.setAdmin(true);
AuthorizationInfo aci = authorizationInfo(trillian);
Collection<String> roles = aci.getRoles();
assertNotNull(roles);
assertEquals(2, roles.size());
assertTrue(roles.contains(Role.ADMIN));
assertTrue(roles.contains(Role.USER));
}
/**
* Method description
*
*/
@Test
public void testAuthorizationDefaultUserPermissions()
{
User trillian = createSampleUser();
AuthorizationInfo ai = authorizationInfo(trillian);
Collection<Permission> permissions = ai.getObjectPermissions();
assertNotNull(permissions);
assertTrue(permissions.isEmpty());
}
/**
* Method description
*
*/
@Test
public void testAuthorizationGroupPermissions()
{
User trillian = createSampleUser();
String g1 = "g1";
String g2 = "g2";
Group g3 = new Group("xml", "g3");
Group g4 = new Group("xml", "g4");
Repository r1 = RepositoryTestData.create42Puzzle();
prepareRepo(r1, g1, PermissionType.READ, true);
Repository r2 = RepositoryTestData.createHappyVerticalPeopleTransporter();
prepareRepo(r2, g2, PermissionType.WRITE, true);
Repository r3 = RepositoryTestData.createHeartOfGold();
prepareRepo(r3, g3, PermissionType.OWNER);
Repository r4 = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
Set<Repository> repositories = ImmutableSet.of(r1, r2, r3, r4);
ScmRealm realm = createRealm(trillian, ImmutableSet.of(g1, g2),
ImmutableSet.of(g3, g4), repositories);
AuthenticationInfo aui = realm.getAuthenticationInfo(token(trillian));
AuthorizationInfo aci = realm.doGetAuthorizationInfo(aui.getPrincipals());
Collection<Permission> permissions = aci.getObjectPermissions();
assertNotNull(permissions);
assertFalse(permissions.isEmpty());
assertEquals(3, permissions.size());
containPermission(permissions, r1, PermissionType.READ);
containPermission(permissions, r2, PermissionType.WRITE);
containPermission(permissions, r3, PermissionType.OWNER);
}
/**
* Method description
*
*/
@Test
public void testAuthorizationUserPermissions()
{
User trillian = createSampleUser();
Repository r1 = RepositoryTestData.create42Puzzle();
prepareRepo(r1, trillian, PermissionType.READ);
Repository r2 = RepositoryTestData.createHappyVerticalPeopleTransporter();
prepareRepo(r2, trillian, PermissionType.WRITE);
Repository r3 = RepositoryTestData.createHeartOfGold();
prepareRepo(r3, trillian, PermissionType.OWNER);
Repository r4 = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
Set<Repository> repositories = ImmutableSet.of(r1, r2, r3, r4);
ScmRealm realm = createRealm(trillian, null, null, repositories);
AuthenticationInfo aui = realm.getAuthenticationInfo(token(trillian));
AuthorizationInfo aci = realm.doGetAuthorizationInfo(aui.getPrincipals());
Collection<Permission> permissions = aci.getObjectPermissions();
assertNotNull(permissions);
assertFalse(permissions.isEmpty());
assertEquals(3, permissions.size());
containPermission(permissions, r1, PermissionType.READ);
containPermission(permissions, r2, PermissionType.WRITE);
containPermission(permissions, r3, PermissionType.OWNER);
}
/**
* Method description
*
*/
@Test
public void testAuthorizationUserRoles()
{
AuthorizationInfo aci = authorizationInfo(createSampleUser());
Collection<String> roles = aci.getRoles();
assertNotNull(roles);
assertEquals(1, roles.size());
assertEquals(Role.USER, roles.iterator().next());
}
/**
* Method description
*
*/
@Test(expected = AccountException.class)
public void testFailedAuthentication()
{
User trillian = createSampleUser();
ScmRealm realm = createRealm(trillian);
realm.getAuthenticationInfo(token(trillian.getId(), "hobbo"));
}
/**
* Method description
*
*/
@Test
public void testSimpleAuthentication()
{
User trillian = createSampleUser();
//J-
ScmRealm realm = createRealm(
trillian,
ImmutableSet.of("g1", "g2"),
ImmutableSet.of(new Group("xml", "g3"), new Group("xml", "g4")),
null
);
//J+
AuthenticationInfo ai = realm.getAuthenticationInfo(token(trillian));
assertNotNull(ai);
PrincipalCollection collection = ai.getPrincipals();
assertNotNull(collection);
assertFalse(collection.isEmpty());
assertEquals(trillian.getId(), collection.getPrimaryPrincipal());
assertEquals(trillian, collection.oneByType(User.class));
GroupNames groups = collection.oneByType(GroupNames.class);
assertNotNull(groups);
assertFalse(groups.getCollection().isEmpty());
assertEquals(5, groups.getCollection().size());
//J-
assertThat(groups,
containsInAnyOrder("g1", "g2", "g3", "g4", GroupNames.AUTHENTICATED)
);
//J+
}
/**
* Method description
*
*
* @param user
*
* @return
*/
private AuthorizationInfo authorizationInfo(User user)
{
ScmRealm realm = createRealm(user);
AuthenticationInfo aui = realm.getAuthenticationInfo(token(user));
AuthorizationInfo aci = realm.doGetAuthorizationInfo(aui.getPrincipals());
assertNotNull(aci);
return aci;
}
/**
* Method description
*
*
* @param permissions
* @param repository
* @param type
*/
private void containPermission(Collection<Permission> permissions,
Repository repository, PermissionType type)
{
assertTrue(
permissions.contains(new RepositoryPermission(repository.getId(), type)));
}
/**
* Method description
*
*
* @param user
*
* @return
*/
private ScmRealm createRealm(User user)
{
return createRealm(user, null, null, null);
}
/**
* Method description
*
*
*
* @param user
* @param authenticationGroups
* @param dbGroups
* @param repositories
* @return
*/
@SuppressWarnings("unchecked")
private ScmRealm createRealm(User user,
Collection<String> authenticationGroups, Collection<Group> dbGroups,
Collection<Repository> repositories)
{
UserManager userManager = mock(UserManager.class);
GroupManager groupManager = mock(GroupManager.class);
if (dbGroups != null)
{
when(groupManager.getGroupsForMember(user.getId())).thenReturn(dbGroups);
}
RepositoryDAO repositoryDAO = mock(RepositoryDAO.class);
if (repositories != null)
{
when(repositoryDAO.getAll()).thenReturn(repositories);
}
UserDAO userDAO = mock(UserDAO.class);
when(userDAO.get(user.getId())).thenReturn(user);
HttpSession session = mock(HttpSession.class);
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getSession(true)).thenReturn(session);
Provider<HttpServletRequest> requestProvider =
new Provider<HttpServletRequest>()
{
@Override
public HttpServletRequest get()
{
return request;
}
};
final HttpServletResponse response = mock(HttpServletResponse.class);
Provider<HttpServletResponse> responseProvider =
new Provider<HttpServletResponse>()
{
@Override
public HttpServletResponse get()
{
return response;
}
};
//J-
AuthenticationManager authManager = mock(AuthenticationManager.class);
when(
authManager.authenticate(
eq(requestProvider.get()),
eq(responseProvider.get()),
eq(user.getId()),
eq(user.getPassword())
)
).thenReturn(
new AuthenticationResult(user, authenticationGroups, AuthenticationState.SUCCESS)
);
when(
authManager.authenticate(
eq(requestProvider.get()),
eq(responseProvider.get()),
eq(user.getId()),
argThat(
not(user.getPassword())
)
)
).thenReturn(
AuthenticationResult.FAILED
);
when(
authManager.authenticate(
eq(requestProvider.get()),
eq(responseProvider.get()),
argThat(
not(user.getName())
),
anyString()
)
).thenReturn(
AuthenticationResult.NOT_FOUND
);
SecuritySystem securitySystem = mock(SecuritySystem.class);
when(
securitySystem.getPermissions(Mockito.any(Predicate.class))
).thenReturn(
Collections.EMPTY_LIST
);
CacheManager cacheManager = new MapCacheManager();
AuthorizationCollector collector = new AuthorizationCollector(
cacheManager,
repositoryDAO,
securitySystem,
new RepositoryPermissionResolver()
);
LoginAttemptHandler dummyLoginAttemptHandler = new LoginAttemptHandler()
{
@Override
public void beforeAuthentication(AuthenticationToken token)
throws AuthenticationException {}
@Override
public void onSuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result) throws AuthenticationException {}
@Override
public void onUnsuccessfulAuthentication(AuthenticationToken token,
AuthenticationResult result) throws AuthenticationException {}
};
return new ScmRealm(
new ScmConfiguration(),
dummyLoginAttemptHandler,
collector,
// cacheManager,
userManager,
groupManager,
userDAO,
authManager,
null,
requestProvider,
responseProvider
);
//J+
}
/**
* Method description
*
*
* @return
*/
private User createSampleUser()
{
User trillian = UserTestData.createTrillian();
trillian.setPassword("moppo123");
return trillian;
}
/**
* Method description
*
*
* @return
*/
private String id()
{
return String.valueOf(counter.incrementAndGet());
}
/**
* Method description
*
*
* @param repository
* @param user
* @param type
*/
private void prepareRepo(Repository repository, User user,
PermissionType type)
{
prepareRepo(repository, user.getId(), type, false);
}
/**
* Method description
*
*
* @param repository
* @param group
* @param type
*/
private void prepareRepo(Repository repository, Group group,
PermissionType type)
{
prepareRepo(repository, group.getId(), type, true);
}
/**
* Method description
*
*
* @param repository
* @param name
* @param type
* @param groupPermission
*/
private void prepareRepo(Repository repository, String name,
PermissionType type, boolean groupPermission)
{
repository.setId(id());
List<sonia.scm.repository.Permission> permissions =
ImmutableList.of(new sonia.scm.repository.Permission(name, type,
groupPermission));
repository.setPermissions(permissions);
}
/**
* Method description
*
*
* @param user
*
* @return
*/
private AuthenticationToken token(User user)
{
return new UsernamePasswordToken(user.getId(), user.getPassword());
}
/**
* Method description
*
*
* @param username
* @param password
*
* @return
*/
private AuthenticationToken token(String username, String password)
{
return new UsernamePasswordToken(username, password);
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private AtomicLong counter = new AtomicLong();
}

View File

@@ -1,384 +0,0 @@
/**
* 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 com.google.common.collect.ImmutableSet;
import org.junit.Test;
import sonia.scm.AbstractTestBase;
import sonia.scm.SCMContextProvider;
import sonia.scm.cache.MapCacheManager;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.security.MessageDigestEncryptionHandler;
import sonia.scm.user.User;
import sonia.scm.user.UserManager;
import sonia.scm.user.UserTestData;
import sonia.scm.util.MockUtil;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
//~--- JDK imports ------------------------------------------------------------
import java.io.IOException;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
public class ChainAuthenticationManagerTest extends AbstractTestBase
{
/**
* Method description
*
*/
@Test
public void testAuthenticateFailed()
{
manager = createManager();
AuthenticationResult result = manager.authenticate(request, response,
trillian.getName(), "trillian");
assertNull(result);
}
/**
* Method description
*
*/
@Test
public void testAuthenticateNotFound()
{
manager = createManager();
AuthenticationResult result = manager.authenticate(request, response,
"dent", "trillian");
assertNull(result);
}
/**
* Method description
*
*/
@Test
public void testAuthenticateSuccess()
{
manager = createManager();
AuthenticationResult result = manager.authenticate(request, response,
trillian.getName(), "trillian123");
assertNotNull(result);
assertUserEquals(trillian, result.getUser());
assertEquals("trilliansType", result.getUser().getType());
result = manager.authenticate(request, response, perfect.getName(),
"perfect123");
assertNotNull(perfect);
assertUserEquals(perfect, result.getUser());
assertEquals("perfectsType", result.getUser().getType());
}
/**
* Method description
*
*/
@Test
public void testAuthenticationOrder()
{
User trillian = UserTestData.createTrillian();
trillian.setPassword("trillian123");
SingleUserAuthenticaionHandler a1 =
new SingleUserAuthenticaionHandler("a1", trillian);
SingleUserAuthenticaionHandler a2 =
new SingleUserAuthenticaionHandler("a2", trillian);
manager = createManager("a2", false, a1, a2);
AuthenticationResult result = manager.authenticate(request, response,
trillian.getName(), "trillian123");
assertNotNull(result);
assertEquals(AuthenticationState.SUCCESS, result.getState());
assertEquals("a2", result.getUser().getType());
}
/**
* Method description
*
*/
@Test
public void testStopChain()
{
ChainAuthenticatonManager cam = createManager("", false);
assertTrue(cam.stopChain(new AuthenticationResult(perfect)));
assertTrue(cam.stopChain(AuthenticationResult.FAILED));
assertFalse(cam.stopChain(AuthenticationResult.NOT_FOUND));
cam = createManager("", true);
assertTrue(cam.stopChain(new AuthenticationResult(perfect)));
assertFalse(cam.stopChain(AuthenticationResult.FAILED));
assertFalse(cam.stopChain(AuthenticationResult.NOT_FOUND));
}
/**
* Method description
*
*
* @throws Exception
*/
@Override
protected void postSetUp() throws Exception
{
request = MockUtil.getHttpServletRequest();
response = MockUtil.getHttpServletResponse();
}
/**
* Method description
*
*
* @throws Exception
*/
@Override
protected void preTearDown() throws Exception
{
manager.close();
}
/**
* Method description
*
*
* @param user
* @param other
*/
private void assertUserEquals(User user, User other)
{
assertEquals(user.getName(), other.getName());
assertEquals(user.getDisplayName(), other.getDisplayName());
assertEquals(user.getMail(), other.getMail());
}
/**
* Method description
*
*
* @return
*/
private ChainAuthenticatonManager createManager()
{
perfect = UserTestData.createPerfect();
perfect.setPassword("perfect123");
trillian = UserTestData.createTrillian();
trillian.setPassword("trillian123");
return createManager("", false,
new SingleUserAuthenticaionHandler("perfectsType", perfect),
new SingleUserAuthenticaionHandler("trilliansType", trillian));
}
/**
* Method description
*
*
* @param defaultType
* @param skipFailedAuthenticators
* @param handlers
*
* @return
*/
private ChainAuthenticatonManager createManager(String defaultType,
boolean skipFailedAuthenticators, AuthenticationHandler... handlers)
{
if ( handlers == null || handlers.length == 0 ){
//J-
handlers = new AuthenticationHandler[]{
new SingleUserAuthenticaionHandler("perfectsType", perfect),
new SingleUserAuthenticaionHandler("trilliansType", trillian)
};
//J+
}
ScmConfiguration configuration = new ScmConfiguration();
configuration.setSkipFailedAuthenticators(skipFailedAuthenticators);
Set<AuthenticationHandler> handlerSet = ImmutableSet.copyOf(handlers);
UserManager userManager = mock(UserManager.class);
when(userManager.getDefaultType()).thenReturn(defaultType);
manager = new ChainAuthenticatonManager(configuration, userManager,
handlerSet, new MessageDigestEncryptionHandler(), new MapCacheManager());
manager.init(contextProvider);
return manager;
}
//~--- inner classes --------------------------------------------------------
/**
* Class description
*
*
* @version Enter version here..., 2010-12-07
* @author Sebastian Sdorra
*/
private static class SingleUserAuthenticaionHandler
implements AuthenticationHandler
{
/**
* Constructs ...
*
*
* @param type
* @param user
*/
public SingleUserAuthenticaionHandler(String type, User user)
{
this.type = type;
this.user = user;
}
//~--- methods ------------------------------------------------------------
/**
* Method description
*
*
* @param request
* @param response
* @param username
* @param password
*
* @return
*/
@Override
public AuthenticationResult authenticate(HttpServletRequest request,
HttpServletResponse response, String username, String password)
{
AuthenticationResult result = null;
if (username.equals(user.getName()))
{
if (password.equals(user.getPassword()))
{
result = new AuthenticationResult(user.clone());
}
else
{
result = AuthenticationResult.FAILED;
}
}
else
{
result = AuthenticationResult.NOT_FOUND;
}
return result;
}
/**
* Method description
*
*
* @throws IOException
*/
@Override
public void close() throws IOException {}
/**
* Method description
*
*
* @param context
*/
@Override
public void init(SCMContextProvider context) {}
//~--- get methods --------------------------------------------------------
/**
* Method description
*
*
* @return
*/
@Override
public String getType()
{
return type;
}
//~--- fields -------------------------------------------------------------
/** Field description */
private final String type;
/** Field description */
private final User user;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private ChainAuthenticatonManager manager;
/** Field description */
private User perfect;
/** Field description */
private HttpServletRequest request;
/** Field description */
private HttpServletResponse response;
/** Field description */
private User trillian;
}

View File

@@ -1,172 +0,0 @@
/**
* 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 org.junit.Test;
import sonia.scm.AbstractTestBase;
import sonia.scm.security.EncryptionHandler;
import sonia.scm.security.MessageDigestEncryptionHandler;
import sonia.scm.store.JAXBStoreFactory;
import sonia.scm.store.StoreFactory;
import sonia.scm.user.DefaultUserManager;
import sonia.scm.user.User;
import sonia.scm.user.UserTestData;
import sonia.scm.user.xml.XmlUserDAO;
import sonia.scm.util.MockUtil;
import static org.junit.Assert.*;
//~--- JDK imports ------------------------------------------------------------
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
*
* @author Sebastian Sdorra
*/
public class DefaultAuthenticationHandlerTest extends AbstractTestBase
{
/**
* Method description
*
*/
@Test
public void testAuthenticateFailed()
{
AuthenticationResult result = handler.authenticate(request, reponse,
slarti.getName(), "otherPWD");
assertNotNull(result);
assertTrue(result.getState() == AuthenticationState.FAILED);
assertNull(result.getUser());
}
/**
* Method description
*
*/
@Test
public void testAuthenticateNotFound()
{
AuthenticationResult result = handler.authenticate(request, reponse,
"notSlarti", "otherPWD");
assertNotNull(result);
assertTrue(result.getState() == AuthenticationState.NOT_FOUND);
assertNull(result.getUser());
}
/**
* Method description
*
*/
@Test
public void testAuthenticateSuccess()
{
AuthenticationResult result = handler.authenticate(request, reponse,
slarti.getName(), "slartisPWD");
assertNotNull(result);
assertTrue(result.getState() == AuthenticationState.SUCCESS);
assertNotNull(result.getUser());
assertEquals(slarti.getName(), result.getUser().getName());
assertEquals(slarti.getDisplayName(), result.getUser().getDisplayName());
assertEquals(slarti.getMail(), result.getUser().getMail());
}
/**
* Method description
*
*
* @throws Exception
*/
@Override
protected void postSetUp() throws Exception
{
EncryptionHandler enc = new MessageDigestEncryptionHandler();
slarti = UserTestData.createSlarti();
slarti.setPassword(enc.encrypt("slartisPWD"));
StoreFactory storeFactory = new JAXBStoreFactory();
storeFactory.init(contextProvider);
XmlUserDAO userDAO = new XmlUserDAO(storeFactory);
setSubject(MockUtil.createAdminSubject());
DefaultUserManager userManager = new DefaultUserManager(userDAO);
userManager.init(contextProvider);
userManager.create(slarti);
clearSubject();
handler = new DefaultAuthenticationHandler(userManager, enc);
handler.init(contextProvider);
request = MockUtil.getHttpServletRequest();
reponse = MockUtil.getHttpServletResponse();
}
/**
* Method description
*
*
* @throws Exception
*/
@Override
protected void preTearDown() throws Exception
{
handler.close();
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private DefaultAuthenticationHandler handler;
/** Field description */
private HttpServletResponse reponse;
/** Field description */
private HttpServletRequest request;
/** Field description */
private User slarti;
}