mark: A photo of Mark kneeling on top of the Taal Volcano in the Philippines. It was a long hike. (Default)
Mark Smith ([staff profile] mark) wrote in [site community profile] changelog2009-05-17 02:28 am

[dw-free] crossposter doesn't use LJ-side custom access groups

[commit: http://hg.dwscoalition.org/dw-free/rev/1157d6f4248d]

http://bugs.dwscoalition.org/show_bug.cgi?id=944

Add custom group support to the crossposter. Works based on the name of the
groups, which have to match exactly.

Patch by [personal profile] allen.

Files modified:
  • cgi-bin/DW/External/Account.pm
  • cgi-bin/DW/External/XPostProtocol/LJXMLRPC.pm
--------------------------------------------------------------------------------
diff -r ac5091814431 -r 1157d6f4248d cgi-bin/DW/External/Account.pm
--- a/cgi-bin/DW/External/Account.pm	Sun May 17 02:10:20 2009 +0000
+++ b/cgi-bin/DW/External/Account.pm	Sun May 17 02:28:08 2009 +0000
@@ -250,6 +250,8 @@ sub crosspost {
             # include the (encrypted) current password.
             $auth->{encrypted_password} = $self->password;
         }
+        # add the username to the auth object
+        $auth->{username} = $self->username;
 
         # see if we're posting or editing
         my $xpost_mapping = $self->xpost_string_to_hash($entry->prop('xpost'));
@@ -301,6 +303,8 @@ sub delete_entry {
             # include the (encrypted) current password.
             $auth->{encrypted_password} = $self->password;
         }
+        # add the username to the auth object
+        $auth->{username} = $self->username;
 
         # get the associated post
         my $xpost_mapping = $self->xpost_string_to_hash($entry->prop('xpost'));
diff -r ac5091814431 -r 1157d6f4248d cgi-bin/DW/External/XPostProtocol/LJXMLRPC.pm
--- a/cgi-bin/DW/External/XPostProtocol/LJXMLRPC.pm	Sun May 17 02:10:20 2009 +0000
+++ b/cgi-bin/DW/External/XPostProtocol/LJXMLRPC.pm	Sun May 17 02:28:08 2009 +0000
@@ -37,6 +37,101 @@ sub _skeleton {
     };
 }
 
+# internal xml-rpc call method.
+# FIXME we should probably combine this with the similar method in
+# DW::Worker::ContentImporter::LiveJournal, and move it to a general
+# LJ-XMLRPC library class.
+sub _call_xmlrpc {
+    my ($self, $xmlrpc, $mode, $req) = @_;
+    
+    my $result = eval { $xmlrpc->call("LJ.XMLRPC.$mode", $req) };
+    
+    if ($result) {
+        if ($result->fault) {
+            # error from server
+            return {
+                success => 0,
+                error => $result->faultstring
+            }
+        } else {
+            # success
+            return {
+                success => 1,
+                result => $result->result
+            }
+        }
+    } else {
+        # connection error
+        return {
+            success => 0,
+            error => LJ::Lang::ml("xpost.error.connection", { url => $xmlrpc->get_proxy })
+        } 
+    }
+}
+
+# does the authentication call.
+# FIXME we should probably combine this with the similar method in
+# DW::Worker::ContentImporter::LiveJournal, and move it to a general
+# LJ-XMLRPC library class.
+sub do_auth {
+    my ($self, $xmlrpc, $auth) = @_;
+    
+    # challenge/response for user validation
+    if ($auth->{auth_challenge} && $auth->{auth_response}) {
+        # if we already have them, just return.
+        return {
+            username       => $auth->{username},
+            auth_challenge => $auth->{auth_challenge},
+            auth_response =>  $auth->{auth_response},
+            success => 1
+        };
+    } else {
+        my $challengecall = $self->_call_xmlrpc($xmlrpc, 'getchallenge', {});
+        if ($challengecall->{success}) {
+            my $challenge = $challengecall->{result}->{challenge};
+            return {
+                username       => $auth->{username},
+                auth_challenge => $challenge,
+                auth_response  => md5_hex($challenge . $auth->{encrypted_password}),
+                success        => 1
+            };
+        } else {
+            # just return the result hashref (with error)
+            return $challengecall;
+        }
+    }
+}
+
+# public xml-rpc call method.
+# FIXME we should probably combine this with the similar method in
+# DW::Worker::ContentImporter::LiveJournal, and move it to a general
+# LJ-XMLRPC library class.
+sub call_xmlrpc {
+    my ($self, $proxyurl, $mode, $req, $auth) = @_;
+    
+    my $xmlrpc = eval { XMLRPC::Lite->proxy($proxyurl); };
+    # connection error if no proxy
+    return {
+        success => 0,
+        error => LJ::Lang::ml("xpost.error.connection", { url => $proxyurl })
+    } unless $xmlrpc;
+
+    # get the auth information
+    my $authresp = $self->do_auth($xmlrpc, $auth);
+    # fail if no auth available
+    return $authresp unless $authresp->{success};
+
+    # return the results of the call
+    return $self->_call_xmlrpc($xmlrpc, $mode, {
+        ver            => 1,
+        auth_method    => 'challenge',
+        username       => $authresp->{username},
+        auth_challenge => $authresp->{auth_challenge},
+        auth_response  => $authresp->{auth_response},
+        %{ $req || {} }
+    });
+}
+
 # does a crosspost using the LJ XML-RPC protocol.  returns a hashref
 # with success => 1 and url => the new url on success, or success => 0
 # and error => the error message on failure.
@@ -46,55 +141,14 @@ sub crosspost {
     # get the xml-rpc proxy and start the connection.
     # use the custom serviceurl if available, or the default using the hostname
     my $proxyurl = $extacct->serviceurl || "http://" . $extacct->serverhost . "/interface/xmlrpc";
-    
 
-    my $xmlrpc = eval { XMLRPC::Lite->proxy($proxyurl); };
-    # connection error if no proxy
-    return {
-        success => 0,
-        error => LJ::Lang::ml("xpost.error.connection", { url => $proxyurl })
-    } unless $xmlrpc;
-
-    # challenge/response for user validation
-    my $challenge;
-    my $response;
-    if ($auth->{auth_challenge} && $auth->{auth_response}) {
-        # just use these.
-        $challenge = $auth->{auth_challenge};
-        $response = $auth->{auth_response};
-    } else {
-        my $challengecall = eval { $xmlrpc->call("LJ.XMLRPC.getchallenge"); };
-
-        if ($challengecall) {
-            if ($challengecall->fault) {
-                # error from the server
-                return {
-                    success => 0,
-                    error => $challengecall->faultstring
-                }
-            } else {
-                # success
-                $challenge = $challengecall->result->{challenge};
-            }
-        } else {
-            # connection error
-            return {
-                success => 0,
-                error => LJ::Lang::ml("xpost.error.connection", { url => $proxyurl })
-            } 
-        }
-    
-        # create the response to the challenge
-        $response = md5_hex($challenge . $auth->{encrypted_password});  
-    }
-    
     # load up the req.  if it's a delete, just set event as blank
     my $req;
     if ($delete) {
         $req = { event => '' };
     } else {
         # if it's a post or edit, fully populate the request.
-        $req = $self->entry_to_req($entry, $extacct);
+        $req = $self->entry_to_req($entry, $extacct, $auth);
         # handle disable comments
         if ($extacct->owner->prop('opt_xpost_disable_comments')) {
             if ($req->{props}->{opt_nocomments}) {
@@ -106,42 +160,38 @@ sub crosspost {
         }
     }
     
-    # update the message with the appropriate remote settings
-    $req->{username} = $extacct->username;
-    $req->{auth_method} = 'challenge';
-    $req->{auth_challenge} = $challenge;
-    $req->{auth_response} = $response;
-    $req->{ver} = 1;
-
     # get the correct itemid for edit
     $req->{itemid} = $itemid if $itemid;
 
     # crosspost, update, or delete
-    my $xpostcall = eval { $xmlrpc->call($itemid ? 'LJ.XMLRPC.editevent' : 'LJ.XMLRPC.postevent', $req) };
-
-    if ($xpostcall) {
-        if ($xpostcall->fault) {
-            # error from server
-            return {
-                success => 0,
-                error => $xpostcall->faultstring
-            }
-        } else {
-            # success
-            return {
-                success => 1,
-                url => $xpostcall->result->{url},
-                reference => $xpostcall->result->{itemid}
-            }
+    my $xpost_result = $self->call_xmlrpc($proxyurl, $itemid ? 'editevent' : 'postevent', $req, $auth);
+    if ($xpost_result->{success}) {
+        return {
+            success => 1,
+            url =>$xpost_result->{result}->{url},
+            reference => $xpost_result->{result}->{itemid}
         }
     } else {
-        # connection error
+        return $xpost_result;
+    }
+}
+
+# returns a hash of friends groups.
+sub get_friendgroups {
+    my ($self, $extacct, $auth) = @_;
+
+    # use the custom serviceurl if available, or the default using the hostname
+    my $proxyurl = $extacct->serviceurl || "http://" . $extacct->serverhost . "/interface/xmlrpc";
+
+    my $xpost_result = $self->call_xmlrpc($proxyurl, 'getfriendgroups', {}, $auth);
+    if ($xpost_result->{success}) {
         return {
-            success => 0,
-            error => LJ::Lang::ml("xpost.error.connection", { url => $proxyurl })
-        } 
+            success => 1,
+            friendgroups => $xpost_result->{result}->{friendgroups},
+        }
+    } else {
+        return $xpost_result;
     }
-
 }
 
 # validates that the given server is running a LJ XML-RPC server.
@@ -171,7 +221,7 @@ sub validate_server {
 
 # translates at Entry object into a request for crossposting
 sub entry_to_req {
-    my ($self, $entry, $extacct) = @_;
+    my ($self, $entry, $extacct, $auth) = @_;
 
     # basic parts of the request
     my $req = {
@@ -186,10 +236,15 @@ sub entry_to_req {
         if ($entry->allowmask == 1) {
             $req->{allowmask} = "1";
         } else {
-            # otherwise, it's a custom group.  just make it private for now.
-            $req->{security} = "private";
+            my $allowmask = $self->translate_allowmask($extacct, $auth, $entry);
+            if ($allowmask) {
+                $req->{allowmask} = $allowmask;
+            } else {
+                $req->{security} = "private";
+            }
         }
     }
+
     # set the date.
     my $eventtime = $entry->eventtime_mysql;
     $eventtime =~ /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d)/;
@@ -220,6 +275,43 @@ sub entry_to_req {
     $req = $extacct->externalsite->pre_crosspost_hook( $req );
 
     return $req;
+}
+
+# translates the given allowmask to 
+sub translate_allowmask {
+    my ($self, $extacct, $auth, $entry) = @_;
+ 
+    my $result = $self->get_friendgroups($extacct, $auth);
+    return 0 unless $result->{success};
+
+    # make a name/id map for the extgroups.
+    my %namemap;
+    foreach my $extgroup ( @{ $result->{friendgroups} || [] } ) {
+        $namemap{$extgroup->{name}} = $extgroup->{id};
+    }
+
+    # get the trusted group id list from the given allowmask
+    my %selected_group_ids = ( map { $_ => 1 } grep { $entry->allowmask & ( 1 << $_ ) } 1..60 );
+    return 0 unless keys %selected_group_ids;
+
+    # get all of the available groups for the poster
+    my $groups = $entry->poster->trust_groups || {};
+    return 0 unless keys %$groups;
+
+    # now try to map them
+    my $extmask = 0;
+    foreach my $groupid ( keys %$groups ) {
+        # skip the groups not selected for this entry
+        next unless $selected_group_ids{$groupid};
+
+        # if there is a matching group name on the external
+        # account, then add its group id to the mask.
+        if ( my $id = $namemap{$groups->{$groupid}->{groupname}} ) {
+            $extmask |= ( 1 << $id );
+        }
+    }
+
+    return $extmask;
 }
 
 # cleans the entry text for crossposting
--------------------------------------------------------------------------------