LP#1347774 Anonymous PCRUD mode
authorMike Rylander <mrylander@gmail.com>
Thu, 24 Jul 2014 19:03:37 +0000 (15:03 -0400)
committerMike Rylander <miker@esilibrary.com>
Thu, 25 Sep 2014 21:22:42 +0000 (17:22 -0400)
Support for anonymous access to public (field_safe=true) IDL data
via PCRUD without requiring an authtoken.  To use, pass an authtoken of
ANONYMOUS.

Includes initial CStoreEditor plugin for anon pcrud.

Signed-off-by: Mike Rylander <mrylander@gmail.com>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/c-apps/oils_sql.c
Open-ILS/src/perlmods/lib/OpenILS/Utils/CStoreEditor.pm

index 77cb12c..d29c6f2 100644 (file)
@@ -119,6 +119,7 @@ static ClassInfo* add_joined_class( const char* alias, const char* classname );
 static void clear_query_stack( void );
 
 static const jsonObject* verifyUserPCRUD( osrfMethodContext* );
+static const jsonObject* verifyUserPCRUDfull( osrfMethodContext*, int );
 static int verifyObjectPCRUD( osrfMethodContext*, osrfHash*, const jsonObject*, int );
 static const char* org_tree_root( osrfMethodContext* ctx );
 static jsonObject* single_hash( const char* key, const char* value );
@@ -1406,48 +1407,62 @@ static int verifyObjectClass ( osrfMethodContext* ctx, const jsonObject* param )
        server; otherwise NULL.
 */
 static const jsonObject* verifyUserPCRUD( osrfMethodContext* ctx ) {
+       return verifyUserPCRUDfull( ctx, 0 );
+}
+
+static const jsonObject* verifyUserPCRUDfull( osrfMethodContext* ctx, int anon_ok ) {
 
        // Get the authkey (the first method parameter)
        const char* auth = jsonObjectGetString( jsonObjectGetIndex( ctx->params, 0 ) );
 
-       // See if we have the same authkey, and a user object,
-       // locally cached from a previous call
-       const char* cached_authkey = getAuthkey( ctx );
-       if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
-               const jsonObject* cached_user = getUserLogin( ctx );
-               if( cached_user )
-                       return cached_user;
-       }
-
-       // We have no matching authentication data in the cache.  Authenticate from scratch.
-       jsonObject* auth_object = jsonNewObject( auth );
-
-       // Fetch the user object from the authentication server
-       jsonObject* user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve",
-                       auth_object );
-       jsonObjectFree( auth_object );
-
-       if( !user->classname || strcmp(user->classname, "au" )) {
+       jsonObject* user = NULL;
+
+       // If we are /not/ in anonymous mode
+       if( strcmp( "ANONYMOUS", auth ) ) {
+               // See if we have the same authkey, and a user object,
+               // locally cached from a previous call
+               const char* cached_authkey = getAuthkey( ctx );
+               if( cached_authkey && !strcmp( cached_authkey, auth ) ) {
+                       const jsonObject* cached_user = getUserLogin( ctx );
+                       if( cached_user )
+                               return cached_user;
+               }
 
-               growing_buffer* msg = buffer_init( 128 );
-               buffer_fadd(
-                       msg,
-                       "%s: permacrud received a bad auth token: %s",
-                       modulename,
-                       auth
-               );
+               // We have no matching authentication data in the cache.  Authenticate from scratch.
+               jsonObject* auth_object = jsonNewObject( auth );
+       
+               // Fetch the user object from the authentication server
+               user = oilsUtilsQuickReq( "open-ils.auth", "open-ils.auth.session.retrieve", auth_object );
+               jsonObjectFree( auth_object );
+       
+               if( !user->classname || strcmp(user->classname, "au" )) {
+       
+                       growing_buffer* msg = buffer_init( 128 );
+                       buffer_fadd(
+                               msg,
+                               "%s: permacrud received a bad auth token: %s",
+                               modulename,
+                               auth
+                       );
+       
+                       char* m = buffer_release( msg );
+                       osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
+                                       ctx->request, m );
+       
+                       free( m );
+                       jsonObjectFree( user );
+                       user = NULL;
+               } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
+                       // Failed to set audit information - But note that write_audit_info already set error information.
+                       jsonObjectFree( user );
+                       user = NULL;
+               }
 
-               char* m = buffer_release( msg );
-               osrfAppSessionStatus( ctx->session, OSRF_STATUS_UNAUTHORIZED, "osrfMethodException",
-                               ctx->request, m );
 
-               free( m );
-               jsonObjectFree( user );
-               user = NULL;
-       } else if( writeAuditInfo( ctx, oilsFMGetStringConst( user, "id" ), oilsFMGetStringConst( user, "wsid" ) ) ) {
-               // Failed to set audit information - But note that write_audit_info already set error information.
-               jsonObjectFree( user );
-               user = NULL;
+       } else if ( anon_ok ) { // we /are/ (attempting to be) anonymous
+               user = jsonNewObjectType(JSON_ARRAY);
+               jsonObjectSetClass( user, "aou" );
+               oilsFMSetString(user, "id", "-1");
        }
 
        setUserLogin( ctx, user );
@@ -1503,6 +1518,12 @@ static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const js
                fetch = 1; // MUST go to the db for the object for update and delete
        }
 
+       // In retrieve or search ONLY we allow anon. Later perm checks will fail as they should,
+       // in the face of a fake user but required permissions.
+       int anon_ok = 0;
+       if( *method_type == 'r' )
+               anon_ok = 1;
+
        // Get the appropriate permacrud entry from the IDL, depending on method type
        osrfHash* pcrud = osrfHashGet( osrfHashGet( class, "permacrud" ), method_type );
        if( !pcrud ) {
@@ -1527,9 +1548,9 @@ static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const js
        }
 
        // Get the user id, and make sure the user is logged in
-       const jsonObject* user = verifyUserPCRUD( ctx );
+       const jsonObject* user = verifyUserPCRUDfull( ctx, anon_ok );
        if( !user )
-               return 0;    // Not logged in?  No access.
+               return 0;    // Not logged in or anon?  No access.
 
        int userid = atoi( oilsFMGetStringConst( user, "id" ) );
 
@@ -1544,6 +1565,10 @@ static int verifyObjectPCRUD ( osrfMethodContext* ctx, osrfHash *class, const js
                return 1;
        }
 
+       // But, if there are perms and the user is anonymous ... FAIL
+       if ( -1 == userid )
+               return 0;
+
        // Build a list of org units that own the row.  This is fairly convoluted because there
        // are several different ways that an org unit may own the row, as defined by the
        // permacrud entry.
index 4ad858f..9954610 100644 (file)
@@ -48,6 +48,30 @@ use base qw/Exporter/;
 push @EXPORT_OK, ( 'new_editor', 'new_rstore_editor' );
 %EXPORT_TAGS = ( funcs => [ qw/ new_editor new_rstore_editor / ] );
 
+our $personality = 'open-ils.cstore';
+
+sub personality { 
+    my( $self, $app ) = @_;
+    $personality = $app if $app;
+    init() if $app; # rewrite if we changed personalities
+    return $personality;
+}
+
+sub import {
+    my $class = shift;
+
+    my @super_args = ();
+    while ( my $a = shift ) {
+        if ($a eq 'personality') {
+            $class->personality( shift );
+        } else {
+            push @super_args, $a;
+        }
+    }
+
+    return $class->SUPER::import( @super_args );
+}
+
 sub new_editor { return OpenILS::Utils::CStoreEditor->new(@_); }
 
 sub new_rstore_editor { 
@@ -90,7 +114,7 @@ sub DESTROY {
 sub app {
     my( $self, $app ) = @_;
     $self->{app} = $app if $app;
-    $self->{app} = 'open-ils.cstore' unless $self->{app};
+    $self->{app} = $self->personality unless $self->{app};
     return $self->{app};
 }
 
@@ -189,6 +213,7 @@ sub died {
 sub authtoken {
     my( $self, $auth ) = @_;
     $self->{authtoken} = $auth if $auth;
+    return 'ANONYMOUS' if ($self->personality eq 'open-ils.pcrud' and !defined($self->{authtoken}));
     return $self->{authtoken};
 }
 
@@ -418,7 +443,7 @@ sub request {
     if( ($self->{xact} or $always_xact) and 
             $self->session->state != OpenSRF::AppSession::CONNECTED() ) {
         #$logger->error("CStoreEditor lost it's connection!!");
-        throw OpenSRF::EX::ERROR ("CStore connection timed out - transaction cannot continue");
+        throw OpenSRF::EX::ERROR ($self->app." connection timed out - transaction cannot continue");
     }
 
 
@@ -735,7 +760,12 @@ sub runmethod {
     }
 
     my @arg = ( ref($arg) eq 'ARRAY' ) ? @$arg : ($arg);
-    my $method = $self->app.".direct.$type.$action";
+    my $method = '';
+    if ($self->personality eq 'open-ils.pcrud') {
+        $method = $self->app.".$action.$type";
+    } else {
+        $method = $self->app.".direct.$type.$action";
+    }
 
     if( $action eq 'search' ) {
         $method .= '.atomic';
@@ -784,7 +814,8 @@ sub runmethod {
         $self->log_activity($method, $type, $action, $arg);
     }
 
-    if($$options{checkperm}) {
+    # only check perms this way in non-pcrud mode
+    if($self->personality ne 'open-ils.pcrud' and $$options{checkperm}) {
         my $a = ($action eq 'search') ? 'retrieve' : $action;
         my $e = $self->_checkperm($type, $a, $$options{permorg});
         if($e) {
@@ -796,6 +827,9 @@ sub runmethod {
     my $obj; 
     my $err = '';
 
+    # In pcrud mode, sub authtoken returns 'ANONYMOUS' if one is not yet set
+    unshift(@$arg, $self->authtoken) if ($self->personality eq 'open-ils.pcrud');
+
     try {
         $obj = $self->request($method, @arg);
     } catch Error with { $err = shift; };
@@ -840,7 +874,7 @@ sub runmethod {
     }
 
     if( $action eq 'search' ) {
-        $self->log(I, "$type.$action : returned ".scalar(@$obj). " result(s)");
+        $self->log(I, "$method: returned ".scalar(@$obj). " result(s)");
         $self->event(_mk_not_found($type, $arg)) unless @$obj;
     }
 
@@ -884,7 +918,12 @@ sub init {
     my $map = $Fieldmapper::fieldmap;
     for my $object (keys %$map) {
         my $obj  = __fm2meth($object, '_');
-        my $type = __fm2meth($object, '.');
+        my $type;
+        if (__PACKAGE__->personality eq 'open-ils.pcrud') {
+            $type = $object->json_hint;
+        } else {
+            $type = __fm2meth($object, '.');
+        }
         foreach my $command (qw/ update retrieve search create delete batch_retrieve retrieve_all /) {
             eval "sub ${command}_$obj {return shift()->runmethod('$command', '$type', \@_);}\n";
         }
@@ -896,6 +935,18 @@ init();  # Add very many subs to this namespace
 
 sub json_query {
     my( $self, $arg, $options ) = @_;
+
+    if( $self->personality eq 'open-ils.pcrud' ) {
+        $self->event(
+            OpenILS::Event->new(
+                'JSON_QUERY_NOT_ALLOWED',
+                attempted_query => $arg,
+                debug => "json_query is not allowed when using the open-ils.pcrud personality of CStoreEditor" 
+            )
+        );
+        return undef;
+    }
+
     $options ||= {};
     my @arg = ( ref($arg) eq 'ARRAY' ) ? @$arg : ($arg);
     my $method = $self->app.'.json_query.atomic';