Support to get/set/delete/generate an HTTP password via REST

The HTTP password of a user can now be retrieved by GET on
/accounts/<account-id>/password.http.

By PUT on /accounts/<account-id>/password.http a new HTTP password can
be generated or set. Directly setting an HTTP password is only allowed
for Gerrit administrators.

By DELETE on /accounts/<account-id>/password.http the HTTP password can
be cleared.

The WebUI is adapted to use the new REST endpoints to generate/clear
the HTTP password. The old RPCs for this are deleted.

Change-Id: I1424f1d4b45e5409095b51f05c9c6afe26e66800
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
Edwin Kempin
2013-05-28 11:23:10 +02:00
committed by Edwin Kempin
parent 12c35d3ed8
commit d9cdf5eea4
11 changed files with 314 additions and 198 deletions

View File

@@ -222,6 +222,85 @@ Sets the account state to inactive.
If the account was already inactive the response is `404 Not Found`. If the account was already inactive the response is `404 Not Found`.
[[get-http-password]]
Get HTTP Password
~~~~~~~~~~~~~~~~~
[verse]
'GET /accounts/link:#account-id[\{account-id\}]/password.http'
Retrieves the HTTP password of an account.
.Request
----
GET /accounts/john.doe@example.com/password.http HTTP/1.0
----
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
"ETxgpih8xrNs"
----
If the account does not have an HTTP password the response is `404 Not Found`.
[[set-http-password]]
Set/Generate HTTP Password
~~~~~~~~~~~~~~~~~~~~~~~~~~
[verse]
'PUT /accounts/link:#account-id[\{account-id\}]/password.http'
Sets/Generates the HTTP password of an account.
The options for setting/generating the HTTP password must be provided
in the request body inside a link:#http-password-input[
HttpPasswordInput] entity.
.Request
----
PUT /accounts/self/password.http HTTP/1.0
Content-Type: application/json;charset=UTF-8
{
"generate": true
}
----
As response the new HTTP password is returned.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json;charset=UTF-8
)]}'
"ETxgpih8xrNs"
----
If the HTTP password was deleted the response is "`204 No Content`".
[[delete-http-password]]
Delete HTTP Password
~~~~~~~~~~~~~~~~~~~~
[verse]
'DELETE /accounts/link:#account-id[\{account-id\}]/password.http'
Deletes the HTTP password of an account.
.Request
----
DELETE /accounts/self/password.http HTTP/1.0
----
.Response
----
HTTP/1.1 204 No Content
----
[[list-account-emails]] [[list-account-emails]]
List Account Emails List Account Emails
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~
@@ -965,6 +1044,24 @@ Only Gerrit administrators are allowed to add email addresses without
confirmation. confirmation.
|============================== |==============================
[[http-password-input]]
HttpPasswordInput
~~~~~~~~~~~~~~~~~
The `HttpPasswordInput` entity contains information for setting/generating
an HTTP password.
[options="header",width="50%",cols="1,^1,5"]
|============================
|Field Name ||Description
|`generate` |`false` if not set|
Whether a new HTTP password should be generated
|`http_password`|optional|
The new HTTP password. Only Gerrit administrators may set the HTTP
password directly. +
If empty or not set and `generate` is false or not set, the HTTP
password is deleted.
|============================
[[query-limit-info]] [[query-limit-info]]
QueryLimitInfo QueryLimitInfo
~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~

View File

@@ -47,16 +47,6 @@ public interface AccountSecurity extends RemoteJsonService {
@SignInRequired @SignInRequired
void changeUserName(String newName, AsyncCallback<VoidResult> callback); void changeUserName(String newName, AsyncCallback<VoidResult> callback);
@Audit
@SignInRequired
void generatePassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> callback);
@Audit
@SignInRequired
void clearPassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> gerritCallback);
@SignInRequired @SignInRequired
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback); void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.client.account; package com.google.gerrit.client.account;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.rpc.NativeString; import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.RestApi; import com.google.gerrit.client.rpc.RestApi;
import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JavaScriptObject;
@@ -31,4 +32,29 @@ public class AccountApi {
new RestApi("/accounts/").id(account).view("emails").id(email) new RestApi("/accounts/").id(account).view("emails").id(email)
.ifNoneMatch().put(in, cb); .ifNoneMatch().put(in, cb);
} }
/** Generate a new HTTP password */
public static void generateHttpPassword(String account,
AsyncCallback<NativeString> cb) {
HttpPasswordInput in = HttpPasswordInput.create();
in.generate(true);
new RestApi("/accounts/").id(account).view("password.http").put(in, cb);
}
/** Clear HTTP password */
public static void clearHttpPassword(String account,
AsyncCallback<VoidResult> cb) {
new RestApi("/accounts/").id(account).view("password.http").delete(cb);
}
private static class HttpPasswordInput extends JavaScriptObject {
final native void generate(boolean g) /*-{ if(g)this.generate=g; }-*/;
static HttpPasswordInput create() {
return createObject().cast();
}
protected HttpPasswordInput() {
}
}
} }

View File

@@ -17,7 +17,9 @@ package com.google.gerrit.client.account;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME; import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
import com.google.gerrit.client.Gerrit; import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.rpc.GerritCallback; import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeString;
import com.google.gerrit.client.rpc.ScreenLoadCallback; import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.reviewdb.client.AccountExternalId; import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickEvent;
@@ -152,10 +154,12 @@ public class MyPasswordScreen extends SettingsScreen {
private void doGeneratePassword() { private void doGeneratePassword() {
if (id != null) { if (id != null) {
enableUI(false); enableUI(false);
Util.ACCOUNT_SEC.generatePassword(id.getKey(), AccountApi.generateHttpPassword("self",
new GerritCallback<AccountExternalId>() { new GerritCallback<NativeString>() {
public void onSuccess(final AccountExternalId result) { @Override
display(result); public void onSuccess(NativeString newPassword) {
id.setPassword(newPassword.asString());
display(id);
} }
@Override @Override
@@ -169,10 +173,12 @@ public class MyPasswordScreen extends SettingsScreen {
private void doClearPassword() { private void doClearPassword() {
if (id != null) { if (id != null) {
enableUI(false); enableUI(false);
Util.ACCOUNT_SEC.clearPassword(id.getKey(), AccountApi.clearHttpPassword("self",
new GerritCallback<AccountExternalId>() { new GerritCallback<VoidResult>() {
public void onSuccess(final AccountExternalId result) { @Override
display(result); public void onSuccess(VoidResult result) {
id.setPassword(null);
display(id);
} }
@Override @Override

View File

@@ -29,8 +29,6 @@ import com.google.gerrit.httpd.rpc.UiRpcModule;
import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.server.CmdLineParserModule; import com.google.gerrit.server.CmdLineParserModule;
import com.google.gerrit.server.RemotePeer; import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.ClearPassword;
import com.google.gerrit.server.account.GeneratePassword;
import com.google.gerrit.server.config.AuthConfig; import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.gerrit.server.config.FactoryModule; import com.google.gerrit.server.config.FactoryModule;
@@ -130,10 +128,8 @@ public class WebModule extends FactoryModule {
bind(GerritConfig.class).toProvider(GerritConfigProvider.class); bind(GerritConfig.class).toProvider(GerritConfigProvider.class);
DynamicSet.setOf(binder(), WebUiPlugin.class); DynamicSet.setOf(binder(), WebUiPlugin.class);
factory(ClearPassword.Factory.class);
install(new AsyncReceiveCommits.Module()); install(new AsyncReceiveCommits.Module());
install(new CmdLineParserModule()); install(new CmdLineParserModule());
factory(GeneratePassword.Factory.class);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider( bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
HttpRemotePeerProvider.class).in(RequestScoped.class); HttpRemotePeerProvider.class).in(RequestScoped.class);

View File

@@ -39,8 +39,6 @@ import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountException; import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager; import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.ChangeUserName; import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.ClearPassword;
import com.google.gerrit.server.account.GeneratePassword;
import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.Realm; import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.contact.ContactStore; import com.google.gerrit.server.contact.ContactStore;
@@ -70,8 +68,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
private final AccountManager accountManager; private final AccountManager accountManager;
private final boolean useContactInfo; private final boolean useContactInfo;
private final ClearPassword.Factory clearPasswordFactory;
private final GeneratePassword.Factory generatePasswordFactory;
private final ChangeUserName.CurrentUser changeUserNameFactory; private final ChangeUserName.CurrentUser changeUserNameFactory;
private final DeleteExternalIds.Factory deleteExternalIdsFactory; private final DeleteExternalIds.Factory deleteExternalIdsFactory;
private final ExternalIdDetailFactory.Factory externalIdDetailFactory; private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
@@ -86,8 +82,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
final EmailTokenVerifier etv, final ProjectCache pc, final EmailTokenVerifier etv, final ProjectCache pc,
final SshKeyCache skc, final AccountByEmailCache abec, final SshKeyCache skc, final AccountByEmailCache abec,
final AccountCache uac, final AccountManager am, final AccountCache uac, final AccountManager am,
final ClearPassword.Factory clearPasswordFactory,
final GeneratePassword.Factory generatePasswordFactory,
final ChangeUserName.CurrentUser changeUserNameFactory, final ChangeUserName.CurrentUser changeUserNameFactory,
final DeleteExternalIds.Factory deleteExternalIdsFactory, final DeleteExternalIds.Factory deleteExternalIdsFactory,
final ExternalIdDetailFactory.Factory externalIdDetailFactory, final ExternalIdDetailFactory.Factory externalIdDetailFactory,
@@ -105,8 +99,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
useContactInfo = contactStore != null && contactStore.isEnabled(); useContactInfo = contactStore != null && contactStore.isEnabled();
this.clearPasswordFactory = clearPasswordFactory;
this.generatePasswordFactory = generatePasswordFactory;
this.changeUserNameFactory = changeUserNameFactory; this.changeUserNameFactory = changeUserNameFactory;
this.deleteExternalIdsFactory = deleteExternalIdsFactory; this.deleteExternalIdsFactory = deleteExternalIdsFactory;
this.externalIdDetailFactory = externalIdDetailFactory; this.externalIdDetailFactory = externalIdDetailFactory;
@@ -179,18 +171,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
} }
} }
@Override
public void generatePassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> callback) {
Handler.wrap(generatePasswordFactory.create(key)).to(callback);
}
@Override
public void clearPassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> callback) {
Handler.wrap(clearPasswordFactory.create(key)).to(callback);
}
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) { public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
externalIdDetailFactory.create().to(callback); externalIdDetailFactory.create().to(callback);
} }

View File

@@ -1,63 +0,0 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.concurrent.Callable;
/** Operation to clear a password for an account. */
public class ClearPassword implements Callable<AccountExternalId> {
public interface Factory {
ClearPassword create(AccountExternalId.Key forUser);
}
private final AccountCache accountCache;
private final ReviewDb db;
private final IdentifiedUser user;
private final AccountExternalId.Key forUser;
@Inject
ClearPassword(final AccountCache accountCache, final ReviewDb db,
final IdentifiedUser user,
@Assisted AccountExternalId.Key forUser) {
this.accountCache = accountCache;
this.db = db;
this.user = user;
this.forUser = forUser;
}
public AccountExternalId call() throws OrmException, NoSuchEntityException {
AccountExternalId id = db.accountExternalIds().get(forUser);
if (id == null || !user.getAccountId().equals(id.getAccountId())) {
throw new NoSuchEntityException();
}
id.setPassword(null);
db.accountExternalIds().update(Collections.singleton(id));
accountCache.evict(user.getAccountId());
return id;
}
}

View File

@@ -1,93 +0,0 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.apache.commons.codec.binary.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.concurrent.Callable;
/** Operation to generate a password for an account. */
public class GeneratePassword implements Callable<AccountExternalId> {
private static final int LEN = 12;
private static final SecureRandom rng;
static {
try {
rng = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Cannot create RNG for password generator", e);
}
}
public interface Factory {
GeneratePassword create(AccountExternalId.Key forUser);
}
private final AccountCache accountCache;
private final ReviewDb db;
private final IdentifiedUser user;
private final AccountExternalId.Key forUser;
@Inject
GeneratePassword(final AccountCache accountCache, final ReviewDb db,
final IdentifiedUser user,
@Assisted AccountExternalId.Key forUser) {
this.accountCache = accountCache;
this.db = db;
this.user = user;
this.forUser = forUser;
}
public AccountExternalId call() throws OrmException, NoSuchEntityException {
AccountExternalId id = db.accountExternalIds().get(forUser);
if (id == null || !user.getAccountId().equals(id.getAccountId())) {
throw new NoSuchEntityException();
}
id.setPassword(generate());
db.accountExternalIds().update(Collections.singleton(id));
accountCache.evict(user.getAccountId());
return id;
}
private String generate() {
byte[] rand = new byte[LEN];
rng.nextBytes(rand);
byte[] enc = Base64.encodeBase64(rand, false);
StringBuilder r = new StringBuilder(LEN);
for (int i = 0; i < LEN; i++) {
if (enc[i] == '=') {
break;
}
r.append((char) enc[i]);
}
return r.toString();
}
}

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
public class GetHttpPassword implements RestReadView<AccountResource> {
private final Provider<CurrentUser> self;
@Inject
GetHttpPassword(Provider<CurrentUser> self) {
this.self = self;
}
@Override
public String apply(AccountResource rsrc) throws AuthException,
ResourceNotFoundException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("not allowed to get http password");
}
AccountState s = rsrc.getUser().state();
if (s.getUserName() == null) {
throw new ResourceNotFoundException();
}
String p = s.getPassword(s.getUserName());
if (p == null) {
throw new ResourceNotFoundException();
}
return p;
}
}

View File

@@ -45,6 +45,9 @@ public class Module extends RestApiModule {
put(EMAIL_KIND).to(PutEmail.class); put(EMAIL_KIND).to(PutEmail.class);
delete(EMAIL_KIND).to(DeleteEmail.class); delete(EMAIL_KIND).to(DeleteEmail.class);
put(EMAIL_KIND, "preferred").to(PutPreferred.class); put(EMAIL_KIND, "preferred").to(PutPreferred.class);
get(ACCOUNT_KIND, "password.http").to(GetHttpPassword.class);
put(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
get(ACCOUNT_KIND, "avatar").to(GetAvatar.class); get(ACCOUNT_KIND, "avatar").to(GetAvatar.class);
get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class); get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class);
child(ACCOUNT_KIND, "capabilities").to(Capabilities.class); child(ACCOUNT_KIND, "capabilities").to(Capabilities.class);

View File

@@ -0,0 +1,123 @@
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.AccountExternalId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.PutHttpPassword.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.apache.commons.codec.binary.Base64;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
static class Input {
String httpPassword;
boolean generate;
}
private static final int LEN = 12;
private static final SecureRandom rng;
static {
try {
rng = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Cannot create RNG for password generator", e);
}
}
private final Provider<CurrentUser> self;
private final Provider<ReviewDb> dbProvider;
private final AccountCache accountCache;
@Inject
PutHttpPassword(Provider<CurrentUser> self, Provider<ReviewDb> dbProvider,
AccountCache accountCache) {
this.self = self;
this.dbProvider = dbProvider;
this.accountCache = accountCache;
}
@Override
public Response<String> apply(AccountResource rsrc, Input input) throws AuthException,
ResourceNotFoundException, ResourceConflictException, OrmException {
if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("not allowed to set HTTP password");
}
if (input == null) {
input = new Input();
}
if (rsrc.getUser().getUserName() == null) {
throw new ResourceConflictException("username must be set");
}
AccountExternalId.Key key =
new AccountExternalId.Key(SCHEME_USERNAME, rsrc.getUser().getUserName());
AccountExternalId id = dbProvider.get().accountExternalIds().get(key);
if (id == null) {
throw new ResourceNotFoundException();
}
String newPassword;
if (input.generate) {
newPassword = generate();
} else {
if (!Strings.isNullOrEmpty(input.httpPassword)
&& !self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("not allowed to set HTTP password directly, "
+ "need to be Gerrit administrator");
}
newPassword = Strings.emptyToNull(input.httpPassword);
}
id.setPassword(newPassword);
dbProvider.get().accountExternalIds().update(Collections.singleton(id));
accountCache.evict(rsrc.getUser().getAccountId());
return Strings.isNullOrEmpty(newPassword)
? Response.<String>none()
: Response.ok(newPassword);
}
private String generate() {
byte[] rand = new byte[LEN];
rng.nextBytes(rand);
byte[] enc = Base64.encodeBase64(rand, false);
StringBuilder r = new StringBuilder(LEN);
for (int i = 0; i < LEN; i++) {
if (enc[i] == '=') {
break;
}
r.append((char) enc[i]);
}
return r.toString();
}
}