Skip to content

Commit

Permalink
Issue/1840 : Fix for Improve password encoding support for LDAP
Browse files Browse the repository at this point in the history
  • Loading branch information
madhukarbharti committed Oct 8, 2020
1 parent d7c62a1 commit 95eaf18
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,24 @@ public void embeddedLdapServerRegistersExpectedAuthenticationProvider()
);
}

@Test
public void base64EmbeddedLdapServerRegistersExpectedAuthenticationProvider()
{
UserDetails ldapUser = ldapUserDetailsService.loadUserByUsername("base64encoded-issue-1840");

assertThat(ldapUser).isInstanceOf(LdapUserDetailsImpl.class);
LdapUserDetails ldapUserDetails = (LdapUserDetailsImpl) ldapUser;

assertThat(ldapUserDetails.getDn()).isEqualTo("uid=base64encoded-issue-1840,ou=Users,dc=carlspring,dc=com");
assertThat(ldapUserDetails.getPassword()).isEqualTo("{bcrypt}JDJhJDEwJGxwd2x4eWp2WEt6TjFjY0NydzJQQnVaeC5lVmVzV2JmbVRic3JDYm9NVS5nc05XVmNaV01p");
assertThat(ldapUserDetails.getUsername()).isEqualTo("base64encoded-issue-1840");
assertThat(((List<SimpleGrantedAuthority>)ldapUser.getAuthorities()))
.contains(
new SimpleGrantedAuthority(SystemRole.REPOSITORY_MANAGER.name()),
new SimpleGrantedAuthority("USER_ROLE")
);
}

public static class TestContextInitializer extends AuthenticationContextInitializer
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ givenName: Martin
surname: Todorov
userPassword: {bcrypt}$2a$10$lpwlxyjvXKzN1ccCrw2PBuZx.eVesWbfmTbsrCboMU.gsNWVcZWMi

dn: uid=base64encoded-issue-1840,ou=Users,dc=carlspring,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
objectClass: person
objectClass: top
uid: base64encoded-issue-1840
cn: Martin Todorov
mail: mtodorov@carlspring.com
givenName: Martin
surname: Todorov
userPassword: {bcrypt}JDJhJDEwJGxwd2x4eWp2WEt6TjFjY0NydzJQQnVaeC5lVmVzV2JmbVRic3JDYm9NVS5nc05XVmNaV01p

dn: uid=stodorov,ou=Users,dc=carlspring,dc=com
objectClass: inetOrgPerson
objectClass: organizationalPerson
Expand Down Expand Up @@ -82,6 +94,7 @@ objectClass: groupOfUniqueNames
uniqueMember: uid=mtodorov,ou=Users,dc=carlspring,dc=com
uniqueMember: uid=stodorov,ou=Users,dc=carlspring,dc=com
uniqueMember: uid=przemyslaw.fusik,ou=Users,dc=carlspring,dc=com
uniqueMember: uid=base64encoded-issue-1840,ou=Users,dc=carlspring,dc=com

# Employees -> Contributors
dn: ou=Contributors, ou=Employees, ou=Groups, dc=carlspring, dc=com
Expand All @@ -90,6 +103,7 @@ cn: Contributors
description: All contributors
objectClass: groupOfUniqueNames
uniqueMember: uid=przemyslaw.fusik,ou=Users,dc=carlspring,dc=com
uniqueMember: uid=base64encoded-issue-1840,ou=Users,dc=carlspring,dc=com

# Employees -> Managers
dn: ou=Managers, ou=Employees, ou=Groups, dc=carlspring, dc=com
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.carlspring.strongbox.configuration;

import javax.annotation.PostConstruct;

import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.util.Base64Utils;

/**
* This class encodes given raw String or Base64 encoded string.
* If given string is Base64 Encoded it will attempt to decode the password and finally delegate it to the password encoder.
*
* @author mbharti
* @date 07/10/20
*/
@Component
public class StrongboxDelegatingPasswordEncoder
implements PasswordEncoder
{

private PasswordEncoder passwordEncoder;

@PostConstruct
private void init()
{
passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

/**
* This Encodes the raw password. In case given rawCharSequence is Base64 encoded,
* then it will decode and then encode the decoded string.
*
* @param rawCharSequence Raw String or Base64 encoded String
* @return Encoded String
*/
@Override
public String encode(CharSequence rawCharSequence)
{
return passwordEncoder.encode(getDecodedString(rawCharSequence));
}

/**
* Verify the encoded password obtained from storage matches the submitted raw password after it too is encoded.
* Returns true if the passwords match, false if they do not. The stored password itself is never decoded.
* In case given rawCharSequence is Base64 encoded, then it decode it first then verifies.
*
* @param rawCharSequence Raw String or Base64 encoded String
* @param encodedString Encoded String
* @return true if the passwords match
* false if the password do not match
*/
@Override
public boolean matches(CharSequence rawCharSequence,
String encodedString)
{
return passwordEncoder.matches(getDecodedString(rawCharSequence), encodedString);
}

private String getDecodedString(CharSequence rawCharSequence)
{
if (rawCharSequence == null)
{
return null;
}

String rawString = rawCharSequence.toString();

try
{
//May throw IllegalArgumentException if raw string contains invalid Base64 characters
String base64DecodedString = new String(Base64Utils.decodeFromString(rawString));

String base64EncodedString = Base64Utils.encodeToString(base64DecodedString.getBytes());

if (rawString.equals(base64EncodedString))
{
return base64DecodedString;
}
}
catch (IllegalArgumentException e)
{
return rawString;
}

return rawString;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@ComponentScan({ "org.carlspring.strongbox.configuration",
"org.carlspring.strongbox.security",
"org.carlspring.strongbox.visitors" })
public class StrongboxSecurityConfig
public class WebSecurityConfig
{

@Bean
@Default
PasswordEncoder passwordEncoder()
{
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
return new StrongboxDelegatingPasswordEncoder();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.carlspring.strongbox.configuration;

import javax.inject.Inject;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.util.Base64Utils;


@SpringBootTest
@ActiveProfiles(profiles = "test")
@ContextConfiguration
class StrongboxDelegatingPasswordEncoderTest
{

@org.springframework.context.annotation.Configuration
@ComponentScan(basePackages = { "org.carlspring.strongbox.security",
"org.carlspring.strongbox.testing",
"org.carlspring.strongbox.configuration" })
public static class SpringConfig
{

}


@Inject
private PasswordEncoder passwordEncoder;

@Test
void testNullEncodeAndMatch()
{
Assertions.assertThrows(NullPointerException.class, () -> passwordEncoder.encode(null));
Assertions.assertTrue(passwordEncoder.matches(null, null));
}

@Test
void encodeAndMatch()
{
String text = "password12";
String base64EncodedString = Base64Utils.encodeToString(text.getBytes());

String normalEncode = passwordEncoder.encode(text);
String base64Encode = passwordEncoder.encode(base64EncodedString);

Assertions.assertTrue(passwordEncoder.matches(text, normalEncode));
Assertions.assertTrue(passwordEncoder.matches(text, base64Encode));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.apache.commons.lang.StringUtils;
import org.carlspring.strongbox.configuration.ConfigurationManager;
import org.carlspring.strongbox.configuration.StrongboxSecurityConfig;
import org.carlspring.strongbox.configuration.WebSecurityConfig;
import org.carlspring.strongbox.converters.RoleFormToRoleConverter;
import org.carlspring.strongbox.converters.RoleListFormToRoleListConverter;
import org.carlspring.strongbox.converters.configuration.ProxyConfigurationFormConverter;
Expand Down Expand Up @@ -76,7 +76,7 @@
"org.carlspring.strongbox.utils",
"org.carlspring.strongbox.actuator" })
@Import({ CommonConfig.class,
StrongboxSecurityConfig.class,
WebSecurityConfig.class,
StorageApiConfig.class,
EventsConfig.class,
StorageCoreConfig.class,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.carlspring.strongbox.controllers.users;

import org.carlspring.strongbox.configuration.StrongboxDelegatingPasswordEncoder;
import org.carlspring.strongbox.controllers.BaseController;
import org.carlspring.strongbox.forms.users.PasswordEncodeForm;
import org.carlspring.strongbox.validation.RequestBodyValidationException;
Expand All @@ -15,7 +16,6 @@
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
Expand All @@ -35,7 +35,7 @@ public class PasswordEncoderController
public static final String INVALID_FORM = "Form contains invalid data!";

@Inject
private PasswordEncoder passwordEncoder;
private StrongboxDelegatingPasswordEncoder passwordEncoder;

@ApiOperation(value = "Encodes submitted raw password")
@ApiResponses(value = @ApiResponse(code = 200, message = "Returns encoded password"))
Expand Down

0 comments on commit 95eaf18

Please sign in to comment.