From: Tara L Andrews Date: Tue, 28 Aug 2012 14:11:26 +0000 (+0200) Subject: add user access control to relationship mapper tool X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=a155a7213f2e6d5dc1ae660a077151409897815a;p=scpubgit%2Fstemmatology.git add user access control to relationship mapper tool --- diff --git a/stemmaweb/lib/stemmaweb/Controller/Relation.pm b/stemmaweb/lib/stemmaweb/Controller/Relation.pm index 0842124..961cfa1 100644 --- a/stemmaweb/lib/stemmaweb/Controller/Relation.pm +++ b/stemmaweb/lib/stemmaweb/Controller/Relation.pm @@ -64,6 +64,11 @@ sub text :Chained('/') :PathPart('relation') :CaptureArgs(1) { $tradition->collation->_set_tradition( $tradition ); $c->model('Directory')->save( $tradition ); } + # Check permissions. Will return 403 if denied, otherwise will + # put the appropriate value in the stash. + my $ok = _check_permission( $c, $tradition ); + return unless $ok; + # See how big the tradition is. Edges are more important than nodes # when it comes to rendering difficulty. my $numnodes = scalar $tradition->collation->readings; @@ -172,6 +177,8 @@ success or 403 on error. sub relationships :Chained('text') :PathPart :Args(0) { my( $self, $c ) = @_; my $tradition = delete $c->stash->{'tradition'}; + my $ok = _check_permission( $c, $tradition ); + return unless $ok; my $collation = $tradition->collation; my $m = $c->model('Directory'); if( $c->request->method eq 'GET' ) { @@ -187,37 +194,50 @@ sub relationships :Chained('text') :PathPart :Args(0) { push( @all_relations, $relhash ); } $c->stash->{'result'} = \@all_relations; - } elsif( $c->request->method eq 'POST' ) { - my $node = $c->request->param('source_id'); - my $target = $c->request->param('target_id'); - my $relation = $c->request->param('rel_type'); - my $note = $c->request->param('note'); - my $scope = $c->request->param('scope'); - - my $opts = { 'type' => $relation, - 'scope' => $scope }; - $opts->{'annotation'} = $note if $note; - - try { - my @vectors = $collation->add_relationship( $node, $target, $opts ); - $c->stash->{'result'} = \@vectors; - $m->save( $tradition ); - } catch( Text::Tradition::Error $e ) { + } else { + # Check write permissions first of all + if( $c->stash->{'permission'} ne 'full' ) { $c->response->status( '403' ); - $c->stash->{'result'} = { 'error' => $e->message }; + $c->stash->{'result'} = { + 'error' => 'You do not have permission to view this tradition.' }; + } elsif( $c->request->method eq 'POST' ) { + unless( $c->stash->{'permission'} eq 'full' ) { + $c->response->status( '403' ); + $c->stash->{'result'} = { + 'error' => 'You do not have permission to view this tradition.' }; + $c->detach( 'View::JSON' ); + } + my $node = $c->request->param('source_id'); + my $target = $c->request->param('target_id'); + my $relation = $c->request->param('rel_type'); + my $note = $c->request->param('note'); + my $scope = $c->request->param('scope'); + + my $opts = { 'type' => $relation, + 'scope' => $scope }; + $opts->{'annotation'} = $note if $note; + + try { + my @vectors = $collation->add_relationship( $node, $target, $opts ); + $c->stash->{'result'} = \@vectors; + $m->save( $tradition ); + } catch( Text::Tradition::Error $e ) { + $c->response->status( '403' ); + $c->stash->{'result'} = { 'error' => $e->message }; + } + } elsif( $c->request->method eq 'DELETE' ) { + my $node = $c->request->param('source_id'); + my $target = $c->request->param('target_id'); + + try { + my @vectors = $collation->del_relationship( $node, $target ); + $m->save( $tradition ); + $c->stash->{'result'} = \@vectors; + } catch( Text::Tradition::Error $e ) { + $c->response->status( '403' ); + $c->stash->{'result'} = { 'error' => $e->message }; + } } - } elsif( $c->request->method eq 'DELETE' ) { - my $node = $c->request->param('source_id'); - my $target = $c->request->param('target_id'); - - try { - my @vectors = $collation->del_relationship( $node, $target ); - $m->save( $tradition ); - $c->stash->{'result'} = \@vectors; - } catch( Text::Tradition::Error $e ) { - $c->response->status( '403' ); - $c->stash->{'result'} = { 'error' => $e->message }; - } } $c->forward('View::JSON'); } @@ -263,6 +283,8 @@ sub _reading_struct { sub readings :Chained('text') :PathPart :Args(0) { my( $self, $c ) = @_; my $tradition = delete $c->stash->{'tradition'}; + my $ok = _check_permission( $c, $tradition ); + return unless $ok; my $collation = $tradition->collation; my $m = $c->model('Directory'); if( $c->request->method eq 'GET' ) { @@ -298,6 +320,12 @@ sub reading :Chained('text') :PathPart :Args(1) { $c->stash->{'result'} = $rdg ? _reading_struct( $rdg ) : { 'error' => "No reading with ID $reading_id" }; } elsif ( $c->request->method eq 'POST' ) { + if( $c->stash->{'permission'} ne 'full' ) { + $c->response->status( '403' ); + $c->stash->{'result'} = { + 'error' => 'You do not have permission to view this tradition.' }; + $c->detach('View::JSON'); + } my $errmsg; # Are we re-lemmatizing? if( $c->request->param('relemmatize') ) { @@ -348,6 +376,25 @@ sub reading :Chained('text') :PathPart :Args(1) { } +sub _check_permission { + my( $c, $tradition ) = @_; + my $user = $c->user_exists ? $c->user->get_object : undef; + if( $user ) { + $c->stash->{'permission'} = 'full' + if( $user->is_admin || $tradition->user->id eq $user->id ); + return 1; + } elsif( $tradition->public ) { + $c->stash->{'permission'} = 'readonly'; + return 1; + } else { + # Forbidden! + $c->response->status( 403 ); + $c->response->body( 'You do not have permission to view this tradition.' ); + $c->detach( 'View::Plain' ); + return 0; + } +} + sub _clean_booleans { my( $rdg, $param, $val ) = @_; if( $rdg->meta->get_attribute( $param )->type_constraint->name eq 'Bool' ) { diff --git a/stemmaweb/root/js/relationship.js b/stemmaweb/root/js/relationship-full.js similarity index 100% rename from stemmaweb/root/js/relationship.js rename to stemmaweb/root/js/relationship-full.js diff --git a/stemmaweb/root/js/relationship-readonly.js b/stemmaweb/root/js/relationship-readonly.js new file mode 100644 index 0000000..9527a10 --- /dev/null +++ b/stemmaweb/root/js/relationship-readonly.js @@ -0,0 +1,637 @@ +var MARGIN=30; +var svg_root = null; +var svg_root_element = null; +var start_element_height = 0; +var reltypes = {}; +var readingdata = {}; + +function getTextPath() { + var currpath = window.location.pathname; + // Get rid of trailing slash + if( currpath.lastIndexOf('/') == currpath.length - 1 ) { + currpath = currpath.slice( 0, currpath.length - 1) + }; + // Get rid of query parameters + if( currpath.lastIndexOf('?') != -1 ) { + currpath = currpath.slice( 0, currpath.lastIndexOf('?') ); + }; + var path_elements = currpath.split('/'); + var textid = path_elements.pop(); + var basepath = path_elements.join( '/' ); + var path_parts = [ basepath, textid ]; + return path_parts; +} + +function getRelativePath() { + var path_parts = getTextPath(); + return path_parts[0]; +} + +function getTextURL( which ) { + var path_parts = getTextPath(); + return path_parts[0] + '/' + path_parts[1] + '/' + which; +} + +function getReadingURL( reading_id ) { + var path_parts = getTextPath(); + return path_parts[0] + '/' + path_parts[1] + '/reading/' + reading_id; +} + +// Make an XML ID into a valid selector +function jq(myid) { + return '#' + myid.replace(/(:|\.)/g,'\\$1'); +} + +// Actions for opening the reading panel +function node_dblclick_listener( evt ) { + // Open the reading dialogue for the given node. + // First get the reading info + var reading_id = $(this).attr('id'); + var reading_info = readingdata[reading_id]; + // and then populate the dialog box with it. + // Set the easy properties first + $('#reading-form').dialog( 'option', 'title', 'Reading information for "' + reading_info['text'] + '"' ); + $('#reading_id').val( reading_id ); + toggle_checkbox( $('#reading_is_nonsense'), reading_info['is_nonsense'] ); + toggle_checkbox( $('#reading_grammar_invalid'), reading_info['grammar_invalid'] ); + // Use .text as a backup for .normal_form + var normal_form = reading_info['normal_form']; + if( !normal_form ) { + normal_form = reading_info['text']; + } + var nfboxsize = 10; + if( normal_form.length > 9 ) { + nfboxsize = normal_form.length + 1; + } + $('#reading_normal_form').attr( 'size', nfboxsize ) + $('#reading_normal_form').val( normal_form ); + // Now do the morphological properties. + morphology_form( reading_info['lexemes'] ); + // and then open the dialog. + $('#reading-form').dialog("open"); +} + +function toggle_checkbox( box, value ) { + if( value == null ) { + value = false; + } + box.attr('checked', value ); +} + +function morphology_form ( lexlist ) { + $('#morphology').empty(); + $.each( lexlist, function( idx, lex ) { + var morphoptions = []; + if( 'wordform_matchlist' in lex ) { + $.each( lex['wordform_matchlist'], function( tdx, tag ) { + var tagstr = stringify_wordform( tag ); + morphoptions.push( tagstr ); + }); + } + var formtag = 'morphology_' + idx; + var formstr = ''; + if( 'form' in lex ) { + formstr = stringify_wordform( lex['form'] ); + } + var form_morph_elements = morph_elements( + formtag, lex['string'], formstr, morphoptions ); + $.each( form_morph_elements, function( idx, el ) { + $('#morphology').append( el ); + }); + }); +} + +function stringify_wordform ( tag ) { + if( tag ) { + var elements = tag.split(' // '); + return elements[1] + ' // ' + elements[2]; + } + return '' +} + +function morph_elements ( formtag, formtxt, currform, morphoptions ) { + var clicktag = '(Click to select)'; + if ( !currform ) { + currform = clicktag; + } + var formlabel = $('