chiark / gitweb /
register/confirm commands, and tests, and docs
authorRichard Kettlewell <rjk@greenend.org.uk>
Fri, 21 Dec 2007 20:24:54 +0000 (20:24 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Fri, 21 Dec 2007 20:24:54 +0000 (20:24 +0000)
doc/disorder_protocol.5.in
lib/client.c
lib/client.h
lib/trackdb.c
lib/trackdb.h
python/disorder.py.in
server/server.c
tests/user.py

index fa98a8144751c888e20cc688be0f78b498af16aa..a1a4af209c5d0df4b7b67a9a6838b51da7718694 100644 (file)
@@ -58,6 +58,10 @@ Creates a new user with the given username and password.  Requires the
 Lists all the files and directories in \fIDIRECTORY\fR in a response body.
 If \fIREGEXP\fR is present only matching files and directories are returned.
 .TP
 Lists all the files and directories in \fIDIRECTORY\fR in a response body.
 If \fIREGEXP\fR is present only matching files and directories are returned.
 .TP
+.B confirm \fICONFIRMATION
+Confirm user registration.  \fICONFIRMATION\fR is as returned from
+\fBregister\fR below.  This command can be used without logging in.
+.TP
 .B cookie \fICOOKIE
 Log a user back in using a cookie created with \fBmake-cookie\fR.
 .TP
 .B cookie \fICOOKIE
 Log a user back in using a cookie created with \fBmake-cookie\fR.
 .TP
@@ -210,6 +214,11 @@ information syntax.
 Request that DisOrder reconfigure itself.   Requires the \fBadmin\fR right.
 command.
 .TP
 Request that DisOrder reconfigure itself.   Requires the \fBadmin\fR right.
 command.
 .TP
+.B register \fIUSER PASSWORD EMAIL
+Register a new user.  Requires the \fBregister\fR right.  The result contains a
+confirmation string; the user will be be able to log in until this has been
+presented back to the server via the \fBconfirm\fR command.
+.TP
 .B remove \fIID\fR
 Remove the track identified by \fIID\fR.  Requires one of the \fBremove
 mine\fR, \fBremove random\fR or \fBremove any\fR rights depending on how the
 .B remove \fIID\fR
 Remove the track identified by \fIID\fR.  Requires one of the \fBremove
 mine\fR, \fBremove random\fR or \fBremove any\fR rights depending on how the
index 739fc20e1cac4a07878f04184c8b11d1ab90171d..39272e6f6953031e25edf4b6741d7df88b1b7bfa 100644 (file)
@@ -697,6 +697,18 @@ int disorder_edituser(disorder_client *c, const char *user,
   return disorder_simple(c, 0, "edituser", user, key, value, (char *)0);
 }
 
   return disorder_simple(c, 0, "edituser", user, key, value, (char *)0);
 }
 
+int disorder_register(disorder_client *c, const char *user,
+                     const char *password, const char *email,
+                     char **confirmp) {
+  return dequote(disorder_simple(c, confirmp, "register",
+                                user, password, email, (char *)0),
+                confirmp);
+}
+
+int disorder_confirm(disorder_client *c, const char *confirm) {
+  return disorder_simple(c, 0, "confirm", confirm, (char *)0);
+}
+
 /*
 Local Variables:
 c-basic-offset:2
 /*
 Local Variables:
 c-basic-offset:2
index f336de6a51600e78dc1ee954af7e23adf58c6272..c685b3db65ee0611d5e1f97ad676bc1f0289f306 100644 (file)
@@ -197,6 +197,10 @@ int disorder_edituser(disorder_client *c, const char *user,
                      const char *key, const char *value);
 int disorder_users(disorder_client *c,
                   char ***vecp, int *nvecp);
                      const char *key, const char *value);
 int disorder_users(disorder_client *c,
                   char ***vecp, int *nvecp);
+int disorder_register(disorder_client *c, const char *user,
+                     const char *password, const char *email,
+                     char **confirmp);
+int disorder_confirm(disorder_client *c, const char *confirm);
 
 #endif /* CLIENT_H */
 
 
 #endif /* CLIENT_H */
 
index 46a875c5a3f48c6e6ed6636c7eb55d614a2f334b..369474857803a919cca8da6cbe6619efd93f3c1a 100644 (file)
@@ -2421,22 +2421,52 @@ static int trusted(const char *user) {
   return n < config->trust.n;
 }
 
   return n < config->trust.n;
 }
 
+/** @brief Return non-zero for a valid username
+ *
+ * Currently we only allow the letters and digits in ASCII.  We could be more
+ * liberal than this but it is a nice simple test.  It is critical that
+ * semicolons are never allowed.
+ */
+static int valid_username(const char *user) {
+  if(!*user)
+    return 0;
+  while(*user) {
+    const uint8_t c = *user++;
+    /* For now we are very strict */
+    if((c >= 'a' && c <= 'z')
+       || (c >= 'A' && c <= 'Z')
+       || (c >= '0' && c <= '9'))
+      /* ok */;
+    else
+      return 0;
+  }
+  return 1;
+}
+
 /** @brief Add a user */
 static int create_user(const char *user,
                        const char *password,
                        const char *rights,
                        const char *email,
 /** @brief Add a user */
 static int create_user(const char *user,
                        const char *password,
                        const char *rights,
                        const char *email,
+                       const char *confirmation,
                        DB_TXN *tid,
                        uint32_t flags) {
   struct kvp *k = 0;
   char s[64];
 
                        DB_TXN *tid,
                        uint32_t flags) {
   struct kvp *k = 0;
   char s[64];
 
+  /* sanity check user */
+  if(!valid_username(user)) {
+    error(0, "invalid username '%s'", user);
+    return -1;
+  }
   /* data for this user */
   if(password)
     kvp_set(&k, "password", password);
   kvp_set(&k, "rights", rights);
   if(email)
     kvp_set(&k, "email", email);
   /* data for this user */
   if(password)
     kvp_set(&k, "password", password);
   kvp_set(&k, "rights", rights);
   if(email)
     kvp_set(&k, "email", email);
+  if(confirmation)
+    kvp_set(&k, "confirmation", confirmation);
   snprintf(s, sizeof s, "%jd", (intmax_t)time(0));
   kvp_set(&k, "created", s);
   return trackdb_putdata(trackdb_usersdb, user, k, tid, flags);
   snprintf(s, sizeof s, "%jd", (intmax_t)time(0));
   kvp_set(&k, "created", s);
   return trackdb_putdata(trackdb_usersdb, user, k, tid, flags);
@@ -2459,7 +2489,8 @@ static int one_old_user(const char *user, const char *password,
     rights = rights_string(config->default_rights|RIGHT_ADMIN|RIGHT_RESCAN);
   else
     rights = rights_string(config->default_rights);
     rights = rights_string(config->default_rights|RIGHT_ADMIN|RIGHT_RESCAN);
   else
     rights = rights_string(config->default_rights);
-  return create_user(user, password, rights, 0/*email*/, tid, DB_NOOVERWRITE);
+  return create_user(user, password, rights, 0/*email*/, 0/*confirmation*/,
+                     tid, DB_NOOVERWRITE);
 }
 
 static int trackdb_old_users_tid(DB_TXN *tid) {
 }
 
 static int trackdb_old_users_tid(DB_TXN *tid) {
@@ -2502,8 +2533,9 @@ void trackdb_create_root(void) {
   gcry_randomize(pwbin, sizeof pwbin, GCRY_STRONG_RANDOM);
   pw = mime_to_base64(pwbin, sizeof pwbin);
   /* Create the root user if it does not exist */
   gcry_randomize(pwbin, sizeof pwbin, GCRY_STRONG_RANDOM);
   pw = mime_to_base64(pwbin, sizeof pwbin);
   /* Create the root user if it does not exist */
-  WITH_TRANSACTION(create_user("root", pw, "all", 0/*email*/, tid,
-                               DB_NOOVERWRITE));
+  WITH_TRANSACTION(create_user("root", pw, "all",
+                               0/*email*/, 0/*confirmation*/,
+                               tid, DB_NOOVERWRITE));
   if(e == 0)
     info("created root user");
 }
   if(e == 0)
     info("created root user");
 }
@@ -2538,11 +2570,12 @@ const char *trackdb_get_password(const char *user) {
 int trackdb_adduser(const char *user,
                     const char *password,
                     rights_type rights,
 int trackdb_adduser(const char *user,
                     const char *password,
                     rights_type rights,
-                    const char *email) {
+                    const char *email,
+                    const char *confirmation) {
   int e;
   const char *r = rights_string(rights);
 
   int e;
   const char *r = rights_string(rights);
 
-  WITH_TRANSACTION(create_user(user, password, r, email,
+  WITH_TRANSACTION(create_user(user, password, r, email, confirmation,
                                tid, DB_NOOVERWRITE));
   if(e) {
     error(0, "cannot created user '%s' because they already exist", user);
                                tid, DB_NOOVERWRITE));
   if(e) {
     error(0, "cannot created user '%s' because they already exist", user);
@@ -2662,6 +2695,49 @@ char **trackdb_listusers(void) {
   return v->vec;
 }
 
   return v->vec;
 }
 
+static int trackdb_confirm_tid(const char *user, const char *confirmation,
+                               DB_TXN *tid) {
+  const char *stored_confirmation;
+  struct kvp *k;
+  int e;
+  
+  if((e = trackdb_getdata(trackdb_usersdb, user, &k, tid)))
+    return e;
+  if(!(stored_confirmation = kvp_get(k, "confirmation"))) {
+    error(0, "already confirmed user '%s'", user);
+    /* DB claims -30,800 to -30,999 so -1 should be a safe bet */
+    return -1;
+  }
+  if(strcmp(confirmation, stored_confirmation)) {
+    error(0, "wrong confirmation string for user '%s'", user);
+    return -1;
+  }
+  /* 'sall good */
+  kvp_set(&k, "confirmation", 0);
+  return trackdb_putdata(trackdb_usersdb, user, k, tid, 0);
+}
+
+/** @brief Confirm a user registration
+ * @param user Username
+ * @param confirmation Confirmation string
+ * @return 0 on success, non-0 on error
+ */
+int trackdb_confirm(const char *user, const char *confirmation) {
+  int e;
+
+  WITH_TRANSACTION(trackdb_confirm_tid(user, confirmation, tid));
+  switch(e) {
+  case 0:
+    info("registration confirmed for user '%s'", user);
+    return 0;
+  case DB_NOTFOUND:
+    error(0, "confirmation for nonexistent user '%s'", user);
+    return -1;
+  default:                              /* already reported */
+    return -1;
+  }
+}
+
 /*
 Local Variables:
 c-basic-offset:2
 /*
 Local Variables:
 c-basic-offset:2
index 2c57e86c9223709845784899ee12e09eff339ffe..d777146cea5f15d898f25c5193f88158c3f02b53 100644 (file)
@@ -162,12 +162,14 @@ const char *trackdb_get_password(const char *user);
 int trackdb_adduser(const char *user,
                     const char *password,
                     rights_type rights,
 int trackdb_adduser(const char *user,
                     const char *password,
                     rights_type rights,
-                    const char *email);
+                    const char *email,
+                    const char *confirmation);
 int trackdb_deluser(const char *user);
 struct kvp *trackdb_getuserinfo(const char *user);
 int trackdb_edituserinfo(const char *user,
                          const char *key, const char *value);
 char **trackdb_listusers(void);
 int trackdb_deluser(const char *user);
 struct kvp *trackdb_getuserinfo(const char *user);
 int trackdb_edituserinfo(const char *user,
                          const char *key, const char *value);
 char **trackdb_listusers(void);
+int trackdb_confirm(const char *user, const char *confirmation);
 
 #endif /* TRACKDB_H */
 
 
 #endif /* TRACKDB_H */
 
index 650671d506c818ee248034ae2c5f922e2af49860..36157a0a8dbb39e065f10bca3ba91b5b7bd087ae 100644 (file)
@@ -881,6 +881,15 @@ class client:
     self._simple("users")
     return self._body()
 
     self._simple("users")
     return self._body()
 
+  def register(self, username, password, email):
+    """Register a user"""
+    res, details = self._simple("register", username, password, email)
+    return _split(details)[0]
+
+  def confirm(self, confirmation):
+    """Confirm a user registration"""
+    res, details = self._simple("confirm", confirmation)
+
   ########################################################################
   # I/O infrastructure
 
   ########################################################################
   # I/O infrastructure
 
index 807a2766cd882f57d2efff6e0f96f1e5a6a65ded..22cf45401b160dce1c8eeded45e337ab2fd3a24e 100644 (file)
@@ -66,6 +66,7 @@
 #include "cache.h"
 #include "unicode.h"
 #include "cookies.h"
 #include "cache.h"
 #include "unicode.h"
 #include "cookies.h"
+#include "mime.h"
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
 
 #ifndef NONCE_SIZE
 # define NONCE_SIZE 16
@@ -1077,7 +1078,8 @@ static int c_revoke(struct conn *c,
 static int c_adduser(struct conn *c,
                     char **vec,
                     int attribute((unused)) nvec) {
 static int c_adduser(struct conn *c,
                     char **vec,
                     int attribute((unused)) nvec) {
-  if(trackdb_adduser(vec[0], vec[1], config->default_rights, 0))
+  if(trackdb_adduser(vec[0], vec[1], config->default_rights,
+                    0/*email*/, 0/*confirmation*/))
     sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
   else
     sink_writes(ev_writer_sink(c->w), "250 User created\n");
     sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
   else
     sink_writes(ev_writer_sink(c->w), "250 User created\n");
@@ -1088,7 +1090,7 @@ static int c_deluser(struct conn *c,
                     char **vec,
                     int attribute((unused)) nvec) {
   if(trackdb_deluser(vec[0]))
                     char **vec,
                     int attribute((unused)) nvec) {
   if(trackdb_deluser(vec[0]))
-    sink_writes(ev_writer_sink(c->w), "550 Cannot deleted user\n");
+    sink_writes(ev_writer_sink(c->w), "550 Cannot delete user\n");
   else
     sink_writes(ev_writer_sink(c->w), "250 User deleted\n");
   return 1;
   else
     sink_writes(ev_writer_sink(c->w), "250 User deleted\n");
   return 1;
@@ -1152,6 +1154,45 @@ static int c_users(struct conn *c,
   return 1;                            /* completed */
 }
 
   return 1;                            /* completed */
 }
 
+static int c_register(struct conn *c,
+                     char **vec,
+                     int attribute((unused)) nvec) {
+  char *buf, *cs;
+  size_t bufsize;
+  int offset;
+
+  /* The confirmation string is base64(username;nonce) */
+  bufsize = strlen(vec[0]) + NONCE_SIZE + 2;
+  buf = xmalloc_noptr(bufsize);
+  offset = byte_snprintf(buf, bufsize, "%s;", vec[0]);
+  gcry_randomize(buf + offset, NONCE_SIZE, GCRY_STRONG_RANDOM);
+  cs = mime_to_base64((uint8_t *)buf, offset + NONCE_SIZE);
+  if(trackdb_adduser(vec[0], vec[1], config->default_rights, vec[2], cs))
+    sink_writes(ev_writer_sink(c->w), "550 Cannot create user\n");
+  else
+    sink_printf(ev_writer_sink(c->w), "252 %s\n", quoteutf8(cs));
+  return 1;
+}
+
+static int c_confirm(struct conn *c,
+                    char **vec,
+                    int attribute((unused)) nvec) {
+  size_t nuser;
+  char *user, *sep;
+
+  if(!(user = mime_base64(vec[0], &nuser))
+     || !(sep = memchr(user, ';', nuser))) {
+    sink_writes(ev_writer_sink(c->w), "550 Malformed confirmation string\n");
+    return 1;
+  }
+  *sep = 0;
+  if(trackdb_confirm(user, vec[0]))
+    sink_writes(ev_writer_sink(c->w), "550 Incorrect confirmation string\n");
+  else
+    sink_writes(ev_writer_sink(c->w), "250 OK\n");
+  return 1;
+}
 static const struct command {
   /** @brief Command name */
   const char *name;
 static const struct command {
   /** @brief Command name */
   const char *name;
@@ -1174,6 +1215,7 @@ static const struct command {
 } commands[] = {
   { "adduser",        2, 2,       c_adduser,        RIGHT_ADMIN|RIGHT__LOCAL },
   { "allfiles",       0, 2,       c_allfiles,       RIGHT_READ },
 } commands[] = {
   { "adduser",        2, 2,       c_adduser,        RIGHT_ADMIN|RIGHT__LOCAL },
   { "allfiles",       0, 2,       c_allfiles,       RIGHT_READ },
+  { "confirm",        1, 1,       c_confirm,        0 },
   { "cookie",         1, 1,       c_cookie,         0 },
   { "deluser",        1, 1,       c_deluser,        RIGHT_ADMIN|RIGHT__LOCAL },
   { "dirs",           0, 2,       c_dirs,           RIGHT_READ },
   { "cookie",         1, 1,       c_cookie,         0 },
   { "deluser",        1, 1,       c_deluser,        RIGHT_ADMIN|RIGHT__LOCAL },
   { "dirs",           0, 2,       c_dirs,           RIGHT_READ },
@@ -1203,6 +1245,7 @@ static const struct command {
   { "random-enabled", 0, 0,       c_random_enabled, RIGHT_READ },
   { "recent",         0, 0,       c_recent,         RIGHT_READ },
   { "reconfigure",    0, 0,       c_reconfigure,    RIGHT_ADMIN },
   { "random-enabled", 0, 0,       c_random_enabled, RIGHT_READ },
   { "recent",         0, 0,       c_recent,         RIGHT_READ },
   { "reconfigure",    0, 0,       c_reconfigure,    RIGHT_ADMIN },
+  { "register",       3, 3,       c_register,       RIGHT_REGISTER|RIGHT__LOCAL },
   { "remove",         1, 1,       c_remove,         RIGHT_REMOVE__MASK },
   { "rescan",         0, 0,       c_rescan,         RIGHT_RESCAN },
   { "resolve",        1, 1,       c_resolve,        RIGHT_READ },
   { "remove",         1, 1,       c_remove,         RIGHT_REMOVE__MASK },
   { "rescan",         0, 0,       c_rescan,         RIGHT_RESCAN },
   { "resolve",        1, 1,       c_resolve,        RIGHT_READ },
index b04e2a285fffe3cdeda9d2f76842ded0dc9bce0e..74f021296181e1d0e71fbb5c8ec7bfecbecf8c4b 100755 (executable)
@@ -55,6 +55,22 @@ def test():
     users = c.users()
     assert dtest.lists_have_same_contents(users,
                                           ["fred", "root"])
     users = c.users()
     assert dtest.lists_have_same_contents(users,
                                           ["fred", "root"])
+    print " testing user registration"
+    cs = c.register("joe", "joepass", "joe@nowhere.invalid")
+    print " confirmation string is %s" % cs
+    print " checking unconfirmed user cannot log in"
+    jc = disorder.client(user="joe", password="joepass")
+    try:
+      jc.version()
+      print "*** should not be able to log in before confirmation ***"
+      assert False
+    except disorder.operationError:
+      pass                              # good
+    print " confirming user"
+    c.confirm(cs)
+    print " checking confirmed user can log in"
+    jc = disorder.client(user="joe", password="joepass")
+    jc.version()
 
 if __name__ == '__main__':
     dtest.run()
 
 if __name__ == '__main__':
     dtest.run()