Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue-1840: Improve password encoding support for LDAP #1909

Merged
merged 2 commits into from
Dec 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 7 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ services:
- ./:/workspace
working_dir: /workspace
openldap:
image: osixia/openldap:1.3.0
image: osixia/openldap:1.4.0
ports:
- 53389:389
environment:
- LDAP_BACKEND=bdb
# uid=admin,ou=system,dc=carlspring,dc=com
- LDAP_ADMIN_PASSWORD=secret
- LDAP_BACKEND=mdb
# cn=admin,dc=carlspring,dc=com
- LDAP_ADMIN_PASSWORD=password
- LDAP_READONLY_USER=false
- LDAP_DOMAIN=carlspring.com
# Check http://www.openldap.org/doc/admin24/slapdconf2.html for increased level of logging
Expand All @@ -38,5 +38,7 @@ services:
- LDAP_RFC2307BIS_SCHEMA=true
- LDAP_TLS=false
volumes:
- ./strongbox-security/strongbox-authentication-providers/strongbox-ldap-authentication-provider/src/test/resources:/container/service/slapd/assets/config/bootstrap/ldif/custom
# Manually map files which need to be imported to allow for importing from multi-modules.
- ./strongbox-testing/strongbox-testing-core/src/main/resources/ldap/00-strongbox-base.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/00-strongbox-base.ldif
- ./strongbox-security/strongbox-authentication-providers/strongbox-ldap-authentication-provider/src/test/resources/ldap/10-issue-1840.ldif:/container/service/slapd/assets/config/bootstrap/ldif/custom/10-issue-1840.ldif
command: --copy-service --loglevel trace
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ strongbox:
enabled: true
ldap:
url: ldap://127.0.0.1:53389/dc=carlspring,dc=com
managerDn: uid=admin,ou=system,dc=carlspring,dc=com
managerPassword: secret
managerDn: cn=admin,dc=carlspring,dc=com
managerPassword: password
rolesMapping:
- externalRole: Admins
strongboxRole: ADMIN
Expand All @@ -30,6 +30,7 @@ strongbox:
- uid={0},ou=Users
userSearchBase: ou=Users
userSearchFilter: (uid={0})
userPasswordEncoded: false
authorities:
groupSearchBase: ou=Groups
groupSearchFilter: (uniqueMember={0})
Expand Down
13 changes: 12 additions & 1 deletion strongbox-security/strongbox-authentication-providers/README.md
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
Strongbox authentication providers enumerates all supported builin authentication providers.
# strongbox-authentication-providers

Strongbox authentication providers enumerates all supported built-in authentication providers.

NOTE:

There is currently some conflicting terminology due to Spring's conventions:

* `Authentication Provider` in Spring is a mechanism which provides some sort of authentication credentials
(i.e. Basic Authentication).
* `Authentication Provider` in Strongbox is a `database` of some sort, which provides the users to authenticate against.

Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
Strongbox LDAP authentication provider.
# strongbox-ldap-authentication-provider

This module provides the functionality to authenticate users using an LDAP server as the data source.

## Manual testing with OpenLDAP

Sometimes you might need to do manual testing with an OpenLDAP server. In the `project root` path we already have
`docker-compose.yml` which has everything you need to proceed. The only thing you need is an installed Docker
([guide here](https://docs.docker.com/get-docker/)).

Terminal 1:

1. `cd project root`
2. `docker-compose up openldap` (if you've made changes to the ldif files you might need to
`docker-compose up --force-recreate openldap` instead)

Terminal 2:

1. `cd project root`
2. `mvn clean install -DskipTests`
3. `mvn -pl strongbox-web-core spring-boot:run`

Browser:

1. Open `http://localhost:48080/`
2. Go over the security settings
3. Testing using curl should return `Status 200`
```
curl -I -u an-existing-ldap-user http://localhost:48080/api/account
```

Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package org.carlspring.strongbox.authentication.api.ldap;

import java.util.Base64;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.codec.Utf8;
import org.springframework.security.ldap.userdetails.LdapUserDetailsMapper;
import org.springframework.util.StringUtils;

/**
* This class handles password base64 decoding based on the property strongbox.authentication.ldap.userPasswordEncoded.
*
* <p>
* When set to true will handle these possible cases:
* {ALG}base64.encode(md5/sha1/bcrypt(mypassword))
* base64.encode({ALG}md5/sha1/bcrypt(mypassword))
* <p>
*
* <p>
* When set to false will handle the ordinary case:
* {ALG}md5/sha1/bcrypt(mypassword)
* </p>
*
* @author mbharti
* @date 19/10/20
*/
public class CustomLdapUserDetailsMapper
extends LdapUserDetailsMapper
{

private static final String PREFIX = "{";

private static final String SUFFIX = "}";

private static final String EMPTY_STRING = "";

private static final Logger logger = LoggerFactory.getLogger(CustomLdapUserDetailsMapper.class);

private boolean isUserPasswordEncoded;

protected String mapPassword(Object passwordValue)
{
String passwordValueString = super.mapPassword(passwordValue);

if (!isUserPasswordEncoded())
{
return passwordValueString;
}

return decodeBase64EncodedPassword(passwordValueString);
}

private String decodeBase64EncodedPassword(String prefixEncodedPasswordString)
{
try
{
String algorithmUsed = extractId(prefixEncodedPasswordString);
String extractBase64EncodedHash = prefixEncodedPasswordString;

if (!StringUtils.isEmpty(algorithmUsed))
{
extractBase64EncodedHash = extractEncodedPassword(prefixEncodedPasswordString);

return PREFIX + algorithmUsed + SUFFIX + decodeBase64EncodedHashWithHex(extractBase64EncodedHash);
}
else
{
return new String(Base64.getDecoder().decode(Utf8.encode(extractBase64EncodedHash)));
}
}
catch (Exception e)
{
logger.warn("Failed to match password after decoding base64encoded hash after algorithm", e);

return prefixEncodedPasswordString;
}
}

private String decodeBase64EncodedHashWithHex(String base64EncodedHash)
{
try
{
return new String(Hex.encode(Base64.getDecoder().decode(Utf8.encode(base64EncodedHash))));
}
catch (Exception ex)
{
logger.warn("decode hash using base64! " + ex.getMessage(), ex);
}

return base64EncodedHash;
}

private String extractEncodedPassword(String prefixEncodedPassword)
{
int start = prefixEncodedPassword.indexOf(SUFFIX);

return prefixEncodedPassword.substring(start + 1);
}

private String extractId(String prefixEncodedPassword)
{
int start = prefixEncodedPassword.indexOf(PREFIX);

if (start != 0)
{
return EMPTY_STRING;
}

int end = prefixEncodedPassword.indexOf(SUFFIX, start);

if (end < 0)
{
return EMPTY_STRING;
}

return prefixEncodedPassword.substring(start + 1, end);
}


/**
* Getting value whether Base64EncodedPassword is enabled or not
*
* @return boolean
*/
public boolean isUserPasswordEncoded()
{
return isUserPasswordEncoded;
}


/**
* Setting value whether Base64EncodedPassword is enabled or not
*
* @param userPasswordEncoded
*/
public void setUserPasswordEncoded(boolean userPasswordEncoded)
{
isUserPasswordEncoded = userPasswordEncoded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class LdapAuthenticationConfigurationManager
public static final String USER_DN_PATTERNS = "userDnPatterns";
public static final String ROLES_MAPPING = "rolesMapping";
public static final String CONVERT_TO_UPPER_CASE = "convertToUpperCase";
public static final String USER_PASSWORD_ENCODED = "userPasswordEncoded";
public static final String ROLE_PREFIX = "rolePrefix";
public static final String GROUP_ROLE_ATTRIBUTE = "groupRoleAttribute";
public static final String GROUP_SEARCH_FILTER = "groupSearchFilter";
Expand Down Expand Up @@ -160,6 +161,7 @@ public Map<String, Object> map(LdapConfiguration source)
.collect(Collectors.toList()));

result.put(USER_DN_PATTERNS, source.getUserDnPatternList());
result.put(USER_PASSWORD_ENCODED, source.isUserPasswordEncoded());

return result;
}
Expand Down Expand Up @@ -189,6 +191,7 @@ public LdapConfiguration map(Map<String, Object> source)
result.setUrl((String) source.get(URL));
result.setManagerDn((String) source.get(MANAGER_DN));
result.setManagerPassword(String.valueOf(source.get(MANAGER_PASSWORD)));
result.setUserPasswordEncoded(Boolean.TRUE.equals(source.get(USER_PASSWORD_ENCODED)));

LdapUserSearch userSearch = new LdapUserSearch();
userSearch.setUserSearchBase((String) source.get(USER_SEARCH_BASE));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ public class LdapConfiguration

private String managerPassword;

private boolean userPasswordEncoded;

private LdapAuthoritiesConfiguration authoritiesConfiguration = new LdapAuthoritiesConfiguration();

private LdapUserSearch userSearch = new LdapUserSearch();
Expand Down Expand Up @@ -118,4 +120,14 @@ public void setEnableProvider(boolean enableProvider)
{
this.enableProvider = enableProvider;
}

public boolean isUserPasswordEncoded()
{
return userPasswordEncoded;
}

public void setUserPasswordEncoded(boolean userPasswordEncoded)
{
this.userPasswordEncoded = userPasswordEncoded;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
</bean>
</constructor-arg>
<constructor-arg ref="ldapAuthoritiesPopulator"/>
<property name="userDetailsMapper" ref="customUserDetailsMapper"/>
</bean>

<bean id="customUserDetailsMapper" class="org.carlspring.strongbox.authentication.api.ldap.CustomLdapUserDetailsMapper">
<property name="userPasswordEncoded" value="${strongbox.authentication.ldap.userPasswordEncoded}"/>
</bean>

</beans>
</beans>