This commit is contained in:
Mohamed Karray
2018-12-06 17:38:03 +01:00
7 changed files with 61 additions and 31 deletions

View File

@@ -11,7 +11,7 @@ type State = {
passwordConfirmationFailed: boolean passwordConfirmationFailed: boolean
}; };
type Props = { type Props = {
passwordChanged: string => void, passwordChanged: (string, boolean) => void,
passwordValidator?: string => boolean, passwordValidator?: string => boolean,
// Context props // Context props
t: string => string t: string => string
@@ -98,14 +98,12 @@ class PasswordConfirmation extends React.Component<Props, State> {
); );
}; };
isValid = () => {
return this.state.passwordValid && !this.state.passwordConfirmationFailed
};
propagateChange = () => { propagateChange = () => {
if ( this.props.passwordChanged(this.state.password, this.isValid());
this.state.password &&
this.state.passwordValid &&
!this.state.passwordConfirmationFailed
) {
this.props.passwordChanged(this.state.password);
}
}; };
} }

View File

@@ -21,7 +21,8 @@ type State = {
password: string, password: string,
loading: boolean, loading: boolean,
error?: Error, error?: Error,
passwordChanged: boolean passwordChanged: boolean,
passwordValid: boolean
}; };
class ChangeUserPassword extends React.Component<Props, State> { class ChangeUserPassword extends React.Component<Props, State> {
@@ -35,7 +36,8 @@ class ChangeUserPassword extends React.Component<Props, State> {
passwordConfirmationError: false, passwordConfirmationError: false,
validatePasswordError: false, validatePasswordError: false,
validatePassword: "", validatePassword: "",
passwordChanged: false passwordChanged: false,
passwordValid: false
}; };
} }
@@ -83,6 +85,10 @@ class ChangeUserPassword extends React.Component<Props, State> {
} }
}; };
isValid = () => {
return this.state.oldPassword && this.state.passwordValid;
};
render() { render() {
const { t } = this.props; const { t } = this.props;
const { loading, passwordChanged, error } = this.state; const { loading, passwordChanged, error } = this.state;
@@ -118,7 +124,7 @@ class ChangeUserPassword extends React.Component<Props, State> {
key={this.state.passwordChanged ? "changed" : "unchanged"} key={this.state.passwordChanged ? "changed" : "unchanged"}
/> />
<SubmitButton <SubmitButton
disabled={!this.state.password} disabled={!this.isValid()}
loading={loading} loading={loading}
label={t("password.submit")} label={t("password.submit")}
/> />
@@ -126,8 +132,8 @@ class ChangeUserPassword extends React.Component<Props, State> {
); );
} }
passwordChanged = (password: string) => { passwordChanged = (password: string, passwordValid: boolean) => {
this.setState({ ...this.state, password }); this.setState({ ...this.state, password, passwordValid: (!!password && passwordValid) });
}; };
onClose = () => { onClose = () => {

View File

@@ -19,7 +19,8 @@ type State = {
password: string, password: string,
loading: boolean, loading: boolean,
error?: Error, error?: Error,
passwordChanged: boolean passwordChanged: boolean,
passwordValid: boolean
}; };
class SetUserPassword extends React.Component<Props, State> { class SetUserPassword extends React.Component<Props, State> {
@@ -32,7 +33,8 @@ class SetUserPassword extends React.Component<Props, State> {
passwordConfirmationError: false, passwordConfirmationError: false,
validatePasswordError: false, validatePasswordError: false,
validatePassword: "", validatePassword: "",
passwordChanged: false passwordChanged: false,
passwordValid: false
}; };
} }
@@ -104,7 +106,7 @@ class SetUserPassword extends React.Component<Props, State> {
key={this.state.passwordChanged ? "changed" : "unchanged"} key={this.state.passwordChanged ? "changed" : "unchanged"}
/> />
<SubmitButton <SubmitButton
disabled={!this.state.password} disabled={!this.state.passwordValid}
loading={loading} loading={loading}
label={t("user-form.submit")} label={t("user-form.submit")}
/> />
@@ -112,8 +114,8 @@ class SetUserPassword extends React.Component<Props, State> {
); );
} }
passwordChanged = (password: string) => { passwordChanged = (password: string, passwordValid: boolean) => {
this.setState({ ...this.state, password }); this.setState({ ...this.state, password, passwordValid: (!!password && passwordValid) });
}; };
onClose = () => { onClose = () => {

View File

@@ -22,7 +22,8 @@ type State = {
user: User, user: User,
mailValidationError: boolean, mailValidationError: boolean,
nameValidationError: boolean, nameValidationError: boolean,
displayNameValidationError: boolean displayNameValidationError: boolean,
passwordValid: boolean
}; };
class UserForm extends React.Component<Props, State> { class UserForm extends React.Component<Props, State> {
@@ -41,7 +42,8 @@ class UserForm extends React.Component<Props, State> {
}, },
mailValidationError: false, mailValidationError: false,
displayNameValidationError: false, displayNameValidationError: false,
nameValidationError: false nameValidationError: false,
passwordValid: false
}; };
} }
@@ -61,7 +63,6 @@ class UserForm extends React.Component<Props, State> {
isValid = () => { isValid = () => {
const user = this.state.user; const user = this.state.user;
const passwordValid = this.props.user ? !this.isFalsy(user.password) : true;
return !( return !(
this.state.nameValidationError || this.state.nameValidationError ||
this.state.mailValidationError || this.state.mailValidationError ||
@@ -69,7 +70,7 @@ class UserForm extends React.Component<Props, State> {
this.isFalsy(user.name) || this.isFalsy(user.name) ||
this.isFalsy(user.displayName) || this.isFalsy(user.displayName) ||
this.isFalsy(user.mail) || this.isFalsy(user.mail) ||
passwordValid !this.state.passwordValid
); );
}; };
@@ -166,9 +167,10 @@ class UserForm extends React.Component<Props, State> {
}); });
}; };
handlePasswordChange = (password: string) => { handlePasswordChange = (password: string, passwordValid: boolean) => {
this.setState({ this.setState({
user: { ...this.state.user, password } user: { ...this.state.user, password },
passwordValid: !this.isFalsy(password) && passwordValid
}); });
}; };

View File

@@ -51,6 +51,7 @@ import static com.google.common.base.Preconditions.*;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Random;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@@ -88,12 +89,17 @@ public class SecureKeyResolver extends SigningKeyResolverAdapter
*/ */
@Inject @Inject
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SecureKeyResolver(ConfigurationEntryStoreFactory storeFactory) public SecureKeyResolver(ConfigurationEntryStoreFactory storeFactory) {
this(storeFactory, new SecureRandom());
}
SecureKeyResolver(ConfigurationEntryStoreFactory storeFactory, Random random)
{ {
store = storeFactory store = storeFactory
.withType(SecureKey.class) .withType(SecureKey.class)
.withName(STORE_NAME) .withName(STORE_NAME)
.build(); .build();
this.random = random;
} }
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
@@ -112,7 +118,9 @@ public class SecureKeyResolver extends SigningKeyResolverAdapter
SecureKey key = store.get(subject); SecureKey key = store.get(subject);
checkState(key != null, "could not resolve key for subject %s", subject); if (key == null) {
return getSecureKey(subject).getBytes();
}
return key.getBytes(); return key.getBytes();
} }
@@ -161,7 +169,7 @@ public class SecureKeyResolver extends SigningKeyResolverAdapter
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** secure randon */ /** secure randon */
private final SecureRandom random = new SecureRandom(); private final Random random;
/** configuration entry store */ /** configuration entry store */
private final ConfigurationEntryStore<SecureKey> store; private final ConfigurationEntryStore<SecureKey> store;

View File

@@ -1,5 +1,6 @@
package sonia.scm.web.security; package sonia.scm.web.security;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.AuthenticationToken;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -64,7 +65,13 @@ public class TokenRefreshFilter extends HttpFilter {
} }
private void examineToken(HttpServletRequest request, HttpServletResponse response, BearerToken token) { private void examineToken(HttpServletRequest request, HttpServletResponse response, BearerToken token) {
AccessToken accessToken = resolver.resolve(token); AccessToken accessToken;
try {
accessToken = resolver.resolve(token);
} catch (AuthenticationException e) {
LOG.trace("could not resolve token", e);
return;
}
if (accessToken instanceof JwtAccessToken) { if (accessToken instanceof JwtAccessToken) {
refresher.refresh((JwtAccessToken) accessToken) refresher.refresh((JwtAccessToken) accessToken)
.ifPresent(jwtAccessToken -> refreshToken(request, response, jwtAccessToken)); .ifPresent(jwtAccessToken -> refreshToken(request, response, jwtAccessToken));

View File

@@ -44,12 +44,16 @@ import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.store.ConfigurationEntryStore; import sonia.scm.store.ConfigurationEntryStore;
import sonia.scm.store.ConfigurationEntryStoreFactory; import sonia.scm.store.ConfigurationEntryStoreFactory;
import java.util.Random;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.in;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -99,10 +103,11 @@ public class SecureKeyResolverTest
* Method description * Method description
* *
*/ */
@Test(expected = IllegalStateException.class) @Test
public void testResolveSigningKeyBytesWithoutKey() public void testResolveSigningKeyBytesWithoutKey()
{ {
resolver.resolveSigningKeyBytes(null, Jwts.claims().setSubject("test")); byte[] bytes = resolver.resolveSigningKeyBytes(null, Jwts.claims().setSubject("test"));
assertThat(bytes[0]).isEqualTo((byte) 42);
} }
/** /**
@@ -132,7 +137,9 @@ public class SecureKeyResolverTest
assertThat(storeParameters.getType()).isEqualTo(SecureKey.class); assertThat(storeParameters.getType()).isEqualTo(SecureKey.class);
return true; return true;
}))).thenReturn(store); }))).thenReturn(store);
resolver = new SecureKeyResolver(factory); Random random = mock(Random.class);
doAnswer(invocation -> ((byte[]) invocation.getArguments()[0])[0] = 42).when(random).nextBytes(any());
resolver = new SecureKeyResolver(factory, random);
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------