Skip to content

Commit 8080d5a

Browse files
author
James Williams
authored
Password update and expiration support (#197)
## What is the goal of this PR? We've added support for a variety of features that have been implemented in Cluster including administrators having the ability to set passwords, password updates for users, and password expiry. ## What are the changes implemented in this PR? We've provided two new commands: * `user password-set <username>` * `user password-update` We've also added an expiry notice that notifies users whenever they are within 7 days of their password expiring.
1 parent 8122b54 commit 8080d5a

File tree

2 files changed

+111
-27
lines changed

2 files changed

+111
-27
lines changed

TypeDBConsole.java

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ public class TypeDBConsole {
100100
private static final Path TRANSACTION_HISTORY_FILE =
101101
Paths.get(System.getProperty("user.home"), ".typedb-console-transaction-repl-history").toAbsolutePath();
102102
private static final Logger LOG = LoggerFactory.getLogger(TypeDBConsole.class);
103+
104+
private static final int PASSWORD_EXPIRY_WARN_DAYS = 7;
103105
private static final int ONE_HOUR_IN_MILLIS = 60 * 60 * 1000;
104106

105107
private final Printer printer;
@@ -195,8 +197,17 @@ private void runREPLMode(CLIOptions options) {
195197
runUserList(client);
196198
} else if (command.isUserCreate()) {
197199
runUserCreate(client, command.asUserCreate().user(), command.asUserCreate().password());
198-
} else if (command.isUserPassword()) {
199-
runUserPassword(client, command.asUserPassword().user(), command.asUserPassword().password());
200+
} else if (command.isUserPasswordUpdate()) {
201+
REPLCommand.User.PasswordUpdate userPasswordUpdate = command.asUserPasswordUpdate();
202+
runUserPasswordUpdate(client,
203+
options.username,
204+
userPasswordUpdate.passwordOld(),
205+
userPasswordUpdate.passwordNew());
206+
} else if (command.isUserPasswordSet()) {
207+
REPLCommand.User.PasswordSet userPasswordSet = command.asUserPasswordSet();
208+
runUserPasswordSet(client,
209+
userPasswordSet.user(),
210+
userPasswordSet.password());
200211
} else if (command.isUserDelete()) {
201212
runUserDelete(client, command.asUserDelete().user());
202213
} else if (command.isDatabaseList()) {
@@ -256,7 +267,8 @@ private Completers.TreeCompleter getCompleter(TypeDBClient client) {
256267
nodes.add(node(REPLCommand.User.token,
257268
node(REPLCommand.User.List.token),
258269
node(REPLCommand.User.Create.token),
259-
node(REPLCommand.User.Password.token),
270+
node(REPLCommand.User.PasswordUpdate.token),
271+
node(REPLCommand.User.PasswordSet.token),
260272
node(REPLCommand.User.Delete.token,
261273
node(userNameCompleter))
262274
));
@@ -444,6 +456,10 @@ private TypeDBClient createTypeDBClient(CLIOptions options) {
444456
String optCluster = options.cluster();
445457
if (optCluster != null) {
446458
client = TypeDB.clusterClient(set(optCluster.split(",")), createTypeDBCredential(options));
459+
Optional<Long> passwordExpiryDays = client.asCluster().users().get(options.username).passwordExpiryDays();
460+
if (passwordExpiryDays.isPresent() && passwordExpiryDays.get() <= PASSWORD_EXPIRY_WARN_DAYS) {
461+
printer.info("Your password will expire in " + passwordExpiryDays.get() + " days.");
462+
}
447463
} else {
448464
client = TypeDB.coreClient(TypeDB.DEFAULT_ADDRESS);
449465
}
@@ -486,47 +502,68 @@ private boolean runUserList(TypeDBClient client) {
486502
}
487503
}
488504

489-
private boolean runUserCreate(TypeDBClient client, String user, String password) {
505+
private boolean runUserCreate(TypeDBClient client, String username, String password) {
490506
try {
491507
if (!client.isCluster()) {
492508
printer.error("The command 'user create' is only available in TypeDB Cluster.");
493509
return false;
494510
}
495511
TypeDBClient.Cluster clientCluster = client.asCluster();
496-
clientCluster.users().create(user, password);
497-
printer.info("User '" + user + "' created");
512+
clientCluster.users().create(username, password);
513+
printer.info("User '" + username + "' created");
498514
return true;
499515
} catch (TypeDBClientException e) {
500516
printer.error(e.getMessage());
501517
return false;
502518
}
503519
}
504520

505-
private boolean runUserPassword(TypeDBClient client, String user, String password) {
521+
private boolean runUserPasswordUpdate(TypeDBClient client, String username, String passwordOld, String passwordNew) {
506522
try {
507523
if (!client.isCluster()) {
508-
printer.error("The command 'user update' is only available in TypeDB Cluster.");
524+
printer.error("The command 'user password-update' is only available in TypeDB Cluster.");
509525
return false;
510526
}
511527
TypeDBClient.Cluster clientCluster = client.asCluster();
512-
clientCluster.users().passwordSet(user, password);
513-
printer.info("Updated password for user '" + user + "'");
528+
clientCluster.users().get(username).passwordUpdate(passwordOld, passwordNew);
529+
printer.info("Updated password for user '" + username + "'");
514530
return true;
515531
} catch (TypeDBClientException e) {
516532
printer.error(e.getMessage());
517533
return false;
518534
}
519535
}
520536

521-
private boolean runUserDelete(TypeDBClient client, String user) {
537+
private boolean runUserPasswordSet(TypeDBClient client, String username, String password) {
538+
try {
539+
if (!client.isCluster()) {
540+
printer.error("The command 'user password-set' is only available in TypeDB Cluster.");
541+
return false;
542+
}
543+
TypeDBClient.Cluster clientCluster = client.asCluster();
544+
if (clientCluster.users().contains(username)) {
545+
clientCluster.users().passwordSet(username, password);
546+
printer.info("Set password for user '" + username + "'");
547+
return true;
548+
} else {
549+
printer.info("No such user '" + username + "'");
550+
return false;
551+
}
552+
} catch (TypeDBClientException e) {
553+
printer.error(e.getMessage());
554+
return false;
555+
}
556+
}
557+
558+
private boolean runUserDelete(TypeDBClient client, String username) {
522559
try {
523560
if (!client.isCluster()) {
524561
printer.error("The command 'user delete' is only available in TypeDB Cluster.");
525562
return false;
526563
}
527564
TypeDBClient.Cluster clientCluster = client.asCluster();
528-
clientCluster.users().delete(user);
529-
printer.info("User '" + user + "' deleted");
565+
clientCluster.users().delete(username);
566+
printer.info("User '" + username + "' deleted");
530567
return true;
531568
} catch (TypeDBClientException e) {
532569
printer.error(e.getMessage());

command/REPLCommand.java

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,19 @@ default User.Create asUserCreate() {
8080
throw new TypeDBConsoleException(ILLEGAL_CAST);
8181
}
8282

83-
default boolean isUserPassword() {
83+
default boolean isUserPasswordUpdate() {
8484
return false;
8585
}
8686

87-
default User.Password asUserPassword() {
87+
default User.PasswordUpdate asUserPasswordUpdate() {
88+
throw new TypeDBConsoleException(ILLEGAL_CAST);
89+
}
90+
91+
default boolean isUserPasswordSet() {
92+
return false;
93+
}
94+
95+
default User.PasswordSet asUserPasswordSet() {
8896
throw new TypeDBConsoleException(ILLEGAL_CAST);
8997
}
9098

@@ -379,16 +387,50 @@ public User.Create asUserCreate() {
379387
}
380388
}
381389

382-
public static class Password extends REPLCommand.User {
390+
public static class PasswordUpdate extends REPLCommand.User {
391+
392+
public static String token = "password-update";
393+
private static String helpCommand = User.token + " " + token;
394+
private static String description = "Update the password of the current user";
395+
396+
private final String passwordOld;
397+
private final String passwordNew;
398+
399+
public PasswordUpdate(String passwordOld, String passwordNew) {
400+
this.passwordOld = passwordOld;
401+
this.passwordNew = passwordNew;
402+
}
403+
404+
public String passwordOld() {
405+
return passwordOld;
406+
}
407+
408+
public String passwordNew() {
409+
return passwordNew;
410+
}
383411

384-
public static String token = "password";
412+
@Override
413+
public boolean isUserPasswordUpdate() {
414+
return true;
415+
}
416+
417+
@Override
418+
public PasswordUpdate asUserPasswordUpdate() {
419+
return this;
420+
}
421+
}
422+
423+
public static class PasswordSet extends REPLCommand.User {
424+
425+
public static String token = "password-set";
385426
private static String helpCommand = User.token + " " + token + " " + "<username>";
386-
private static String description = "Update the password of user with name <username>";
427+
private static String description = "Set the password of user with name <username>";
387428

388429
private final String user;
430+
389431
private final String password;
390432

391-
public Password(String user, String password) {
433+
public PasswordSet(String user, String password) {
392434
this.user = user;
393435
this.password = password;
394436
}
@@ -402,12 +444,12 @@ public String password() {
402444
}
403445

404446
@Override
405-
public boolean isUserPassword() {
447+
public boolean isUserPasswordSet() {
406448
return true;
407449
}
408450

409451
@Override
410-
public Password asUserPassword() {
452+
public PasswordSet asUserPasswordSet() {
411453
return this;
412454
}
413455
}
@@ -664,7 +706,8 @@ static String createHelpMenu(TypeDBClient client) {
664706
menu.addAll(Arrays.asList(
665707
pair(User.List.helpCommand, User.List.description),
666708
pair(User.Create.helpCommand, User.Create.description),
667-
pair(User.Password.helpCommand, User.Password.description),
709+
pair(User.PasswordUpdate.helpCommand, User.PasswordUpdate.description),
710+
pair(User.PasswordSet.helpCommand, User.PasswordSet.description),
668711
pair(User.Delete.helpCommand, User.Delete.description)));
669712
}
670713

@@ -712,19 +755,23 @@ static REPLCommand readREPLCommand(String line, @Nullable LineReader passwordRea
712755
command = new Help();
713756
} else if (tokens.length == 1 && tokens[0].equals(Clear.token)) {
714757
command = new Clear();
715-
}
716-
else if (tokens.length == 2 && tokens[0].equals(User.token) && tokens[1].equals(User.List.token)) {
758+
} else if (tokens.length == 2 && tokens[0].equals(User.token) && tokens[1].equals(User.List.token)) {
717759
command = new User.List();
718760
} else if (tokens.length == 3 && tokens[0].equals(User.token) && tokens[1].equals(User.Create.token)) {
719761
String name = tokens[2];
720762
if (passwordReader == null) throw new TypeDBConsoleException(UNABLE_TO_READ_PASSWORD_INTERACTIVELY);
721763
String password = Utils.readPassword(passwordReader, "Password: ");
722764
command = new User.Create(name, password);
723-
} else if (tokens.length == 3 && tokens[0].equals(User.token) && tokens[1].equals(User.Password.token)) {
765+
} else if (tokens.length == 2 && tokens[0].equals(User.token) && tokens[1].equals(User.PasswordUpdate.token)) {
766+
if (passwordReader == null) throw new TypeDBConsoleException(UNABLE_TO_READ_PASSWORD_INTERACTIVELY);
767+
String passwordOld = Utils.readPassword(passwordReader, "Old password: ");
768+
String passwordNew = Utils.readPassword(passwordReader, "New password: ");
769+
command = new User.PasswordUpdate(passwordOld, passwordNew);
770+
} else if (tokens.length == 3 && tokens[0].equals(User.token) && tokens[1].equals(User.PasswordSet.token)) {
724771
String name = tokens[2];
725772
if (passwordReader == null) throw new TypeDBConsoleException(UNABLE_TO_READ_PASSWORD_INTERACTIVELY);
726-
String password = Utils.readPassword(passwordReader, "Password: ");
727-
command = new User.Password(name, password);
773+
String newPassword = Utils.readPassword(passwordReader, "New password: ");
774+
command = new User.PasswordSet(name, newPassword);
728775
} else if (tokens.length == 3 && tokens[0].equals(User.token) && tokens[1].equals(User.Delete.token)) {
729776
String name = tokens[2];
730777
command = new User.Delete(name);

0 commit comments

Comments
 (0)