Index: trunk/DNSDB.pm
===================================================================
--- trunk/DNSDB.pm	(revision 83)
+++ trunk/DNSDB.pm	(revision 87)
@@ -240,5 +240,5 @@
 # Update an ACL entry
 # Takes a db handle, type, owner-id, and hashref for the changed permissions.
-##fixme: Must handle case of changing object's permissions from inherited to custom
+##fixme: Must handle case of changing object's permissions from custom to inherited
 sub changePermissions {
   my $dbh = shift;
@@ -246,9 +246,13 @@
   my $id = shift;
   my $newperms = shift;
+  my $inherit = shift || 0;
 
   my $failmsg = '';
 
-  # see if we're switching from inherited to custom
-  my $sth = $dbh->prepare("SELECT (u.permission_id=g.permission_id) AS was_inherited,u.permission_id".
+  # see if we're switching from inherited to custom.  for bonus points,
+  # snag the permid and parent permid anyway, since we'll need the permid
+  # to set/alter custom perms, and both if we're switching from custom to
+  # inherited.
+  my $sth = $dbh->prepare("SELECT (u.permission_id=g.permission_id) AS was_inherited,u.permission_id,g.permission_id".
 	" FROM ".($type eq 'user' ? 'users' : 'groups')." u ".
 	" JOIN groups g ON u.".($type eq 'user' ? '' : 'parent_')."group_id=g.group_id ".
@@ -256,5 +260,5 @@
   $sth->execute($id);
 
-  my ($wasinherited,$permid) = $sth->fetchrow_array;
+  my ($wasinherited,$permid,$parpermid) = $sth->fetchrow_array;
 
 # hack phtoui
@@ -268,25 +272,32 @@
   # Wrap all the SQL in a transaction
   eval {
-    if ($wasinherited) {
+    if ($inherit) {
+
+      $dbh->do("UPDATE ".($type eq 'user' ? 'users' : 'groups')." SET inherit_perm='t',permission_id=? ".
+	"WHERE ".($type eq 'user' ? 'user' : 'group')."_id=?", undef, ($parpermid, $id) );
+      $dbh->do("DELETE FROM permissions WHERE permission_id=?", undef, ($permid) );
+
+    } else {
+
+      if ($wasinherited) {	# munge new permission entry in if we're switching from inherited perms
 ##fixme: need to add semirecursive bit to properly munge inherited permission ID on subgroups and users
-      $dbh->do("INSERT INTO permissions ($permlist) ".
-	"SELECT $permlist FROM permissions WHERE permission_id=?", undef, ($permid) );
-      #$sth = $dbh->prepare($sql);
-      #$sth->execute($permid);
-      $sth = $dbh->prepare("SELECT permission_id FROM ".($type eq 'user' ? 'users' : 'groups').
-	" WHERE ".($type eq 'user' ? 'user' : 'group')."_id=?");
-      $sth->execute($id);
-      ($permid) = $sth->fetchrow_array;
-      $dbh->do("UPDATE permissions SET ".($type eq 'user' ? 'user' : 'group')."_id=? ".
-	"WHERE permission_id=?", undef, ($id,$permid) );
-      $dbh->do("UPDATE ".($type eq 'user' ? 'users' : 'groups')." SET permission_id=? ".
-	"WHERE ".($type eq 'user' ? 'user' : 'group')."_id=?", undef, ($permid,$id) );
-    }
-    foreach (@permtypes) {
-      if (defined ($newperms->{$_})) {
-	$dbh->do("UPDATE permissions SET $_=? WHERE permission_id=?", undef, ($newperms->{$_},$permid) );
-	#$sth->execute($newperms->{$_},$permid);
+# ... if'n'when we have groups with fully inherited permissions.
+        # SQL is coo
+	$dbh->do("INSERT INTO permissions ($permlist,".($type eq 'user' ? 'user' : 'group')."_id) ".
+		"SELECT $permlist,? FROM permissions WHERE permission_id=?", undef, ($id,$permid) );
+	($permid) = $dbh->selectrow_array("SELECT permission_id FROM permissions ".
+		"WHERE ".($type eq 'user' ? 'user' : 'group')."_id=?", undef, ($id) );
+	$dbh->do("UPDATE ".($type eq 'user' ? 'users' : 'groups')." SET inherit_perm='f',permission_id=? ".
+		"WHERE ".($type eq 'user' ? 'user' : 'group')."_id=?", undef, ($permid, $id) );
       }
-    }
+
+      # and now set the permissions we were passed
+      foreach (@permtypes) {
+	if (defined ($newperms->{$_})) {
+	  $dbh->do("UPDATE permissions SET $_=? WHERE permission_id=?", undef, ($newperms->{$_},$permid) );
+	}
+      }
+
+    } # (inherited->)? custom
 
     $dbh->commit;
@@ -295,5 +306,5 @@
     my $msg = $@;
     eval { $dbh->rollback; };
-    return ('FAIL',"$failmsg: $msg");
+    return ('FAIL',"$failmsg: $msg ($permid)");
   } else {
     return ('OK',$permid);
@@ -533,6 +544,8 @@
 	"VALUES ($groupid,?,?,?,?,?,?,?)");
     if ($inherit) {
-##fixme:  fixme!
+      # Duplicate records from parent.  Actually relying on inherited records feels
+      # very fragile, and it would be problematic to roll over at a later time.
       my $sth2 = $dbh->prepare("SELECT host,type,val,distance,weight,port,ttl FROM default_records WHERE group_id=?");
+      $sth2->execute($pargroup);
       while (my @clonedata = $sth2->fetchrow_array) {
 	$sth->execute(@clonedata);
@@ -665,17 +678,26 @@
 
 ## DNSDB::addUser()
-#
+# Add a user.
+# Takes a DB handle, username, group ID, password, state (active/inactive).
+# Optionally accepts:
+#   user type (user/admin)	- defaults to user
+#   permissions string		- defaults to inherit from group
+#      three valid forms:
+#	i		     - Inherit permissions
+#	c:<user_id>	     - Clone permissions from <user_id>
+#	C:<permission list>  - Set these specific permissions
+#   first name			- defaults to username
+#   last name			- defaults to blank
+#   phone			- defaults to blank (could put other data within column def)
+# Returns (OK,OK) on success, (FAIL,<message>) on failure
 sub addUser {
   $errstr = '';
   my $dbh = shift;
-  return ('FAIL',"Need database handle") if !$dbh;
   my $username = shift;
-  return ('FAIL',"Missing username") if !defined($username);
   my $group = shift;
-  return ('FAIL',"Missing group") if !defined($group);
   my $pass = shift;
-  return ('FAIL',"Missing password") if !defined($pass);
   my $state = shift;
-  return ('FAIL',"Need account status") if !defined($state);
+
+  return ('FAIL',"Missing one or more required entries") if !defined($state);
 
   my $type = shift || 'u';	# create limited users by default - fwiw, not sure yet how this will interact with ACLs
@@ -701,10 +723,13 @@
   local $dbh->{RaiseError} = 1;
 
+my $failmsg;
   # Wrap all the SQL in a transaction
   eval {
-    # insert the user...
-    my $sth = $dbh->prepare("INSERT INTO users (group_id,username,password,firstname,lastname,phone,type,status) ".
-	"VALUES (?,?,?,?,?,?,?,?)");
-    $sth->execute($group,$username,unix_md5_crypt($pass),$fname,$lname,$phone,$type,$state);
+    # insert the user...  note we set inherited perms by default since
+    # it's simple and cleans up some other bits of state
+    my $sth = $dbh->prepare("INSERT INTO users ".
+	"(group_id,username,password,firstname,lastname,phone,type,status,permission_id,inherit_perm) ".
+	"VALUES (?,?,?,?,?,?,?,?,(SELECT permission_id FROM permissions WHERE group_id=?),'t')");
+    $sth->execute($group,$username,unix_md5_crypt($pass),$fname,$lname,$phone,$type,$state,$group);
 
     # get the ID...
@@ -713,6 +738,43 @@
     ($user_id) = $sth->fetchrow_array();
 
+# Permissions!  Gotta set'em all!
+    die "Invalid permission string $permstring"
+	if $permstring !~ /^(?:
+		i	# inherit
+		|c:\d+	# clone
+			# custom.  no, the leading , is not a typo
+		|C:(?:,(?:group|user|domain|record|self)_(?:edit|create|delete))+
+		)$/x;
+# bleh.  I'd call another function to do my dirty work, but we're in the middle of a transaction already.
+    if ($permstring ne 'i') {
+      # for cloned or custom permissions, we have to create a new permissions entry.
+      my $clonesrc = $group;
+      if ($permstring =~ /^c:(\d+)/) { $clonesrc = $1; }
+      $dbh->do("INSERT INTO permissions ($permlist,user_id) ".
+	"SELECT $permlist,? FROM permissions WHERE permission_id=".
+	"(SELECT permission_id FROM permissions WHERE ".($permstring =~ /^c:/ ? 'user' : 'group')."_id=?)",
+	undef, ($user_id,$clonesrc) );
+      $dbh->do("UPDATE users SET permission_id=".
+	"(SELECT permission_id FROM permissions WHERE user_id=?) ".
+	"WHERE user_id=?", undef, ($user_id, $user_id) );
+    }
+    if ($permstring =~ /^C:/) {
+      # finally for custom permissions, we set the passed-in permissions (and unset
+      # any that might have been brought in by the clone operation above)
+      my ($permid) = $dbh->selectrow_array("SELECT permission_id FROM permissions WHERE user_id=?",
+	undef, ($user_id) );
+      foreach (@permtypes) {
+	if ($permstring =~ /,$_/) {
+	  $dbh->do("UPDATE permissions SET $_='t' WHERE permission_id=?", undef, ($permid) );
+	} else {
+	  $dbh->do("UPDATE permissions SET $_='f' WHERE permission_id=?", undef, ($permid) );
+	}
+      }
+    }
+
+    $dbh->do("UPDATE users SET inherit_perm='n' WHERE user_id=?", undef, ($user_id) );
+
 ##fixme: add another table to hold name/email for log table?
-die "dying horribly\n";
+#die "dying horribly ($permstring, $user_id)";
 
     # once we get here, we should have suceeded.
@@ -723,5 +785,5 @@
     my $msg = $@;
     eval { $dbh->rollback; };
-    return ('FAIL',$msg);
+    return ('FAIL',$msg." $failmsg");
   } else {
     return ('OK',$user_id);
@@ -762,5 +824,5 @@
   my $pass = shift;
   my $state = shift;
-  my $type = shift;
+  my $type = shift || 'u';
   my $fname = shift || $username;
   my $lname = shift || '';
@@ -1136,5 +1198,4 @@
   my $id = shift;
 
-return "FAIL", "wakka wakka";
   my $sth = $dbh->prepare("DELETE FROM ".($defrec eq 'y' ? 'default_' : '')."records WHERE record_id=?");
   $sth->execute($id);
