diff --git a/LICENSE b/LICENSE.md similarity index 99% rename from LICENSE rename to LICENSE.md index f04cee8..69c19dd 100644 --- a/LICENSE +++ b/LICENSE.md @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/Makefile b/Makefile index 806887f..b0f72fb 100644 --- a/Makefile +++ b/Makefile @@ -42,7 +42,7 @@ TPAGE_ARGS = --define kb_top=$(TARGET) \ TESTS = $(wildcard t/client-tests/*.t) -all: bin service +all: bin compile-typespec service jarfile: gen_java_client $(SERVER_SPEC) org.patricbrc.Workspace java @@ -84,7 +84,7 @@ bin: $(BIN_PERL) $(BIN_SERVICE_PERL) deploy: deploy-client deploy-service deploy-all: deploy-client deploy-service -deploy-client: deploy-libs deploy-scripts +deploy-client: compile-typespec deploy-docs deploy-libs deploy-scripts deploy-service: deploy-dir deploy-monit deploy-libs deploy-service-scripts $(TPAGE) $(TPAGE_ARGS) service/start_service.tt > $(TARGET)/services/$(SERVICE)/start_service diff --git a/Workspace.spec b/Workspace.spec index 3260769..1871369 100644 --- a/Workspace.spec +++ b/Workspace.spec @@ -110,7 +110,7 @@ typedef structure { bool metadata_only; bool adminmode; } get_params; -funcdef get(get_params input) returns (list> output) authentication required; +funcdef get(get_params input) returns (list> output) authentication optional; /* "update_shock_meta" command Description: @@ -138,7 +138,7 @@ funcdef update_auto_meta(update_auto_meta_params input) returns (list objects; } get_download_url_params; -funcdef get_download_url(get_download_url_params input) returns (list urls) authentication required; +funcdef get_download_url(get_download_url_params input) returns (list urls) authentication optional; /* "get_archive_url" command Description: @@ -158,7 +158,7 @@ typedef structure { string archive_name; string archive_type; } get_archive_url_params; -funcdef get_archive_url(get_archive_url_params input) returns (string url); +funcdef get_archive_url(get_archive_url_params input) returns (string url) authentication optional; /* "list" command Description: @@ -182,7 +182,7 @@ typedef structure { mapping> query; bool adminmode; } list_params; -funcdef ls(list_params input) returns (mapping> output) authentication required; +funcdef ls(list_params input) returns (mapping> output) authentication optional; /********** REORGANIZATION FUNCTIONS *******************/ @@ -244,7 +244,7 @@ typedef structure { WorkspacePerm new_global_permission; bool adminmode; } set_permissions_params; -funcdef set_permissions(set_permissions_params input) returns (ObjectMeta output) authentication required; +funcdef set_permissions(set_permissions_params input) returns (list > output) authentication required; /* "list_permissions" command Description: @@ -258,6 +258,6 @@ typedef structure { list objects; bool adminmode; } list_permissions_params; -funcdef list_permissions(list_permissions_params input) returns (mapping > > output) authentication required; +funcdef list_permissions(list_permissions_params input) returns (mapping > > output) authentication optional; }; diff --git a/lib/Bio/P3/Workspace/ScriptHelpers.pm b/lib/Bio/P3/Workspace/ScriptHelpers.pm index b5be6e4..5d8163b 100644 --- a/lib/Bio/P3/Workspace/ScriptHelpers.pm +++ b/lib/Bio/P3/Workspace/ScriptHelpers.pm @@ -9,7 +9,13 @@ use HTTP::Request::Common; use LWP::UserAgent; use Bio::P3::Workspace::WorkspaceClient; use Bio::P3::Workspace::WorkspaceClientExt; -use Bio::ModelSEED::ProbModelSEED::ProbModelSEEDClient; + +our $have_kb_auth = 0; +eval +{ + require Bio::KBase::Auth; + $have_kb_auth = 1; +}; our $defaultWSURL = "http://p3.theseed.org/services/Workspace"; our $defaultAPPURL = "http://p3.theseed.org/services/app_service"; @@ -227,9 +233,12 @@ sub appurl { sub process_paths { my $paths = shift; for (my $i=0; $i < @{$paths}; $i++) { - if ($paths->[$i] !~ /^\// && $paths->[$i] !~ /^PATRICSOLR/) { + if ($paths->[$i] !~ /^\// && $paths->[$i] !~ /^PATRIC:/ && $paths->[$i] !~ /^PUBSEED:/ && $paths->[$i] !~ /^RAST:/ && $paths->[$i] !~ /^REFSEQ:/) { $paths->[$i] = Bio::P3::Workspace::ScriptHelpers::directory().$paths->[$i]; } + while ($paths->[$i] =~ m/[^\/]+\/\.\.\/*/) { + $paths->[$i] =~ s/[^\/]+\/\.\.\/*//g; + } } return $paths; } @@ -303,15 +312,33 @@ sub login { user_id => $params->{user_id}, password => undef }); + # + # Update the KBase .kbase_config file as well. + # + if ($have_kb_auth) + { + Bio::KBase::Auth::SetConfigs(token => $token, + user_id => $params->{user_id}, + password => undef); + } } return $token; } sub logout { - Bio::P3::Workspace::ScriptHelpers::SetConfig({ - token => undef, - user_id => undef + Bio::P3::Workspace::ScriptHelpers::SetConfig({ + token => undef, + user_id => undef }); + # + # Update the KBase .kbase_config file as well. + # + if ($have_kb_auth) + { + Bio::KBase::Auth::SetConfigs(token => undef, + user_id => undef, + password => undef); + } } sub msClient { @@ -321,14 +348,35 @@ sub msClient { } if ($url eq "impl") { require "Bio/ModelSEED/ProbModelSEED/ProbModelSEEDImpl.pm"; - $ENV{KB_DEPLOYMENT_CONFIG} = "/Users/chenry/code/ProbModelSEED/configs/test.cfg"; $Bio::ModelSEED::ProbModelSEED::Service::CallContext = Bio::ModelSEED::ProbModelSEED::Service::CallContext->new(Bio::P3::Workspace::ScriptHelpers::token(),"unknown",Bio::P3::Workspace::ScriptHelpers::user()); my $client = Bio::ModelSEED::ProbModelSEED::ProbModelSEEDImpl->new(); return $client; } + require "Bio/ModelSEED/ProbModelSEED/ProbModelSEEDClient.pm"; return Bio::ModelSEED::ProbModelSEED::ProbModelSEEDClient->new($url,token => Bio::P3::Workspace::ScriptHelpers::token()); } +sub get_workspace_object { + my $ref = shift; + my $options = shift; + my $input = {objects => [$ref]}; + if ($options->{adminmode}) { + $input->{adminmode} = $options->{adminmode}; + } + if ($options->{metadata_only}) { + $input->{metadata_only} = $options->{metadata_only}; + } + my $wc = wsClient(); + my $output = $wc->get($input); + if (defined($output->[0]->[11])) { + my $ua = LWP::UserAgent->new(); + $ua->default_header( "Authorization" => $options->{token} ); + my $res = $ua->get($options->{url}); + $output->[1] = $res->{content}; + } + return $output; +} + sub wsClient { my $url = shift; if (!defined($url)) { diff --git a/lib/Bio/P3/Workspace/Service.pm b/lib/Bio/P3/Workspace/Service.pm index 0cc9c30..c8796e4 100644 --- a/lib/Bio/P3/Workspace/Service.pm +++ b/lib/Bio/P3/Workspace/Service.pm @@ -6,6 +6,7 @@ use Moose; use POSIX; use JSON; use Bio::KBase::Log; +use Class::Load qw(); use Config::Simple; my $get_time = sub { time, 0 }; eval { @@ -44,15 +45,15 @@ our %return_counts = ( our %method_authentication = ( 'create' => 'required', 'update_metadata' => 'required', - 'get' => 'required', + 'get' => 'optional', 'update_auto_meta' => 'required', - 'get_download_url' => 'required', - 'get_archive_url' => 'none', - 'ls' => 'required', + 'get_download_url' => 'optional', + 'get_archive_url' => 'optional', + 'ls' => 'optional', 'copy' => 'required', 'delete' => 'required', 'set_permissions' => 'required', - 'list_permissions' => 'required', + 'list_permissions' => 'optional', ); @@ -149,6 +150,23 @@ sub _build_loggers return $loggers; } +# +# Override method from RPC::Any::Server::JSONRPC +# to eliminate the deprecation warning for Class::MOP::load_class. +# +sub _default_error { + my ($self, %params) = @_; + my $version = $self->default_version; + $version =~ s/\./_/g; + my $error_class = "JSON::RPC::Common::Procedure::Return::Version_${version}::Error"; + Class::Load::load_class($error_class); + my $error = $error_class->new(%params); + my $return_class = "JSON::RPC::Common::Procedure::Return::Version_$version"; + Class::Load::load_class($return_class); + return $return_class->new(error => $error); +} + + #override of RPC::Any::Server sub handle_error { my ($self, $error) = @_; @@ -396,7 +414,7 @@ sub get_method "There is no method package named '$package'."); } - Class::MOP::load_class($module); + Class::Load::load_class($module); } if (!$module->can($method)) { diff --git a/lib/Bio/P3/Workspace/WorkspaceClient.pm b/lib/Bio/P3/Workspace/WorkspaceClient.pm index b9d7c87..9ce19e7 100644 --- a/lib/Bio/P3/Workspace/WorkspaceClient.pm +++ b/lib/Bio/P3/Workspace/WorkspaceClient.pm @@ -35,10 +35,6 @@ sub new { my($class, $url, @args) = @_; - if (!defined($url)) - { - $url = 'http://p3.theseed.org/services/Workspace'; - } my $self = { client => Bio::P3::Workspace::WorkspaceClient::RpcClient->new, @@ -511,7 +507,7 @@ sub get { my($self, @args) = @_; -# Authentication: required +# Authentication: optional if ((my $n = @args) != 1) { @@ -733,7 +729,7 @@ sub get_download_url { my($self, @args) = @_; -# Authentication: required +# Authentication: optional if ((my $n = @args) != 1) { @@ -828,7 +824,7 @@ sub get_archive_url { my($self, @args) = @_; -# Authentication: none +# Authentication: optional if ((my $n = @args) != 1) { @@ -973,7 +969,7 @@ sub ls { my($self, @args) = @_; -# Authentication: required +# Authentication: optional if ((my $n = @args) != 1) { @@ -1314,7 +1310,9 @@ sub delete
 $input is a set_permissions_params
-$output is an ObjectMeta
+$output is a reference to a list where each element is a reference to a list containing 2 items:
+	0: a Username
+	1: a WorkspacePerm
 set_permissions_params is a reference to a hash where the following keys are defined:
 	path has a value which is a FullObjectPath
 	permissions has a value which is a reference to a list where each element is a reference to a list containing 2 items:
@@ -1327,26 +1325,6 @@ FullObjectPath is a string
 Username is a string
 WorkspacePerm is a string
 bool is an int
-ObjectMeta is a reference to a list containing 12 items:
-	0: an ObjectName
-	1: an ObjectType
-	2: a FullObjectPath
-	3: (creation_time) a Timestamp
-	4: an ObjectID
-	5: (object_owner) a Username
-	6: an ObjectSize
-	7: a UserMetadata
-	8: an AutoMetadata
-	9: (user_permission) a WorkspacePerm
-	10: (global_permission) a WorkspacePerm
-	11: (shockurl) a string
-ObjectName is a string
-ObjectType is a string
-Timestamp is a string
-ObjectID is a string
-ObjectSize is an int
-UserMetadata is a reference to a hash where the key is a string and the value is a string
-AutoMetadata is a reference to a hash where the key is a string and the value is a string
 
 
@@ -1355,7 +1333,9 @@ AutoMetadata is a reference to a hash where the key is a string and the value is =begin text $input is a set_permissions_params -$output is an ObjectMeta +$output is a reference to a list where each element is a reference to a list containing 2 items: + 0: a Username + 1: a WorkspacePerm set_permissions_params is a reference to a hash where the following keys are defined: path has a value which is a FullObjectPath permissions has a value which is a reference to a list where each element is a reference to a list containing 2 items: @@ -1368,26 +1348,6 @@ FullObjectPath is a string Username is a string WorkspacePerm is a string bool is an int -ObjectMeta is a reference to a list containing 12 items: - 0: an ObjectName - 1: an ObjectType - 2: a FullObjectPath - 3: (creation_time) a Timestamp - 4: an ObjectID - 5: (object_owner) a Username - 6: an ObjectSize - 7: a UserMetadata - 8: an AutoMetadata - 9: (user_permission) a WorkspacePerm - 10: (global_permission) a WorkspacePerm - 11: (shockurl) a string -ObjectName is a string -ObjectType is a string -Timestamp is a string -ObjectID is a string -ObjectSize is an int -UserMetadata is a reference to a hash where the key is a string and the value is a string -AutoMetadata is a reference to a hash where the key is a string and the value is a string =end text @@ -1503,7 +1463,7 @@ sub list_permissions { my($self, @args) = @_; -# Authentication: required +# Authentication: optional if ((my $n = @args) != 1) { diff --git a/lib/Bio/P3/Workspace/WorkspaceImpl.pm b/lib/Bio/P3/Workspace/WorkspaceImpl.pm index e13c852..edd5a97 100644 --- a/lib/Bio/P3/Workspace/WorkspaceImpl.pm +++ b/lib/Bio/P3/Workspace/WorkspaceImpl.pm @@ -134,7 +134,7 @@ sub _url { sub _error { my($self,$msg) = @_; $msg = "_ERROR_".$msg."_ERROR_"; - Bio::KBase::Exceptions::ArgumentValidationError->throw(error => $msg,method_name => $self->_current_method()); + Bio::KBase::Exceptions::ArgumentValidationError->throw(error => $msg,method_name => "");#$self->_current_method()); } sub _db_path { @@ -165,14 +165,7 @@ sub _user_is_admin { sub _updateDB { my ($self,$name,$query,$update) = @_; - my $data = $self->_mongodb()->run_command({ - findAndModify => $name, - query => $query, - update => $update - }); - if (ref($data) ne "HASH" || !defined($data->{value})) { - return 0; - } + $self->_mongodb()->get_collection($name)->update($query,$update); return 1; } @@ -222,6 +215,7 @@ sub _generate_object_meta { if (defined($obj->{shocknode})) { $shock = $obj->{shocknode}; } + $obj->{autometadata}->{is_folder} = $self->is_folder($obj->{type}); return [$obj->{name},$obj->{type},$path,$obj->{creation_date},$obj->{uuid},$obj->{owner},$obj->{size},$obj->{metadata},$obj->{autometadata},$self->_get_ws_permission($obj->{wsobj}),$obj->{wsobj}->{global_permission},$shock]; } else { return [$obj->{name},"folder","/".$obj->{owner}."/",$obj->{creation_date},$obj->{uuid},$obj->{owner},0,$obj->{metadata},{},$self->_get_ws_permission($obj),$obj->{global_permission},""]; @@ -230,7 +224,7 @@ sub _generate_object_meta { #Retrieving object data from filesystem or giving permission to download shock node** sub _retrieve_object_data { - my ($self,$obj) = @_; + my ($self,$obj,$wsobj) = @_; if ($obj->{folder} == 1) { return ""; } @@ -244,8 +238,12 @@ sub _retrieve_object_data { } close($fh); } else { - my $ua = LWP::UserAgent->new(); - my $res = $ua->put($obj->{shocknode}."/acl/all?users=".$self->_getUsername(),Authorization => "OAuth ".$self->_wsauth()); + if ($wsobj->{global_permission} ne "n") { + $self->_make_shock_node_public($obj->{shocknode}); + } else { + my $ua = LWP::UserAgent->new(); + my $res = $ua->put($obj->{shocknode}."/acl/all?users=".$self->_getUsername(),Authorization => "OAuth ".$self->_wsauth()); + } $data = $obj->{shocknode}; } if (!defined($data)) { @@ -257,7 +255,7 @@ sub _retrieve_object_data { #Validating that the input permissions have a recognizable value** sub _validate_workspace_permission { my ($self,$input) = @_; - if ($input !~ m/^[awron]$/) { + if ($input !~ m/^[awronp]$/) { $self->_error("Input permissions ".$input." invalid!"); } return $input; @@ -308,12 +306,16 @@ sub _validate_object_type { sub _get_ws_permission { my ($self,$wsobj) = @_; + if ($wsobj->{global_permission} eq "p") { + return "p"; + } my $curruser = $self->_getUsername(); if ($wsobj->{owner} eq $curruser) { return "o"; } my $values = { n => 0, + p => 1, r => 1, w => 2, a => 3, @@ -336,6 +338,7 @@ sub _check_ws_permissions { my $perm = $self->_get_ws_permission($wsobj); my $values = { n => 0, + p => 1, r => 1, w => 2, a => 3, @@ -561,20 +564,28 @@ sub _create_shock_node { my $res = $ua->post($self->_shockurl()."/node",Authorization => "OAuth ".$self->_wsauth()); my $json = JSON::XS->new; my $data = $json->decode($res->content); - #print "create shock node output:\n".Data::Dumper->Dump([$data])."\n\n"; my $res = $ua->put($self->_shockurl()."/node/".$data->{data}->{id}."/acl/all?users=".$self->_getUsername(),Authorization => "OAuth ".$self->_wsauth()); - #print "authorizing shock node output:\n".Data::Dumper->Dump([$res])."\n\n"; return $data->{data}->{id}; } +sub _make_shock_node_public { + my ($self,$url) = @_; + my $ua = LWP::UserAgent->new(); + my $res = $ua->get($url."/acl/",Authorization => "OAuth ".$self->_wsauth()); + my $json = JSON::XS->new; + my $data = $json->decode($res->content); + $res = $ua->delete($url."/acl/read?users=".join(",",@{$data->{data}->{read}}),Authorization => "OAuth ".$self->_wsauth()); +} + sub _update_shock_node { my ($self,$object,$force) = @_; - if ($force == 1 || !defined($self->{_shockupdate}->{$object->{uuid}}) || (time() - $self->{_shockupdate}->{$object->{uuid}}) > $self->{_params}->{"update-interval"}) { + if ($force == 1 || + !defined($self->{_shockupdate}->{$object->{uuid}}) || + (time() - $self->{_shockupdate}->{$object->{uuid}}) > $self->{_params}->{"update-interval"}) { my $ua = LWP::UserAgent->new(); my $res = $ua->get($object->{shocknode},Authorization => "OAuth ".$self->_wsauth()); my $json = JSON::XS->new; my $data = $json->decode($res->content); - print Data::Dumper->Dump([$data])."\n"; if (length($data->{data}->{file}->{name}) == 0) { $self->{_shockupdate}->{$object->{uuid}} = time(); } else { @@ -613,7 +624,7 @@ sub _validate_save_objects_before_saving { my $nocreate = 0; #We are creating a workspace if (defined($wsobj)) { - if ($objects->[$i]->[1] eq "folder") { + if ($self->is_folder($objects->[$i]->[1]) == 1) { #We ignore creation of folders that already exist $nocreate = 1; } else { @@ -623,7 +634,7 @@ sub _validate_save_objects_before_saving { } elsif ($user ne $self->_getUsername() && $self->_adminmode() == 0) { #Users can only create their own workspaces $self->_error("Insufficient permissions to create ".$objects->[$i]->[0]); - } elsif ($objects->[$i]->[1] ne "folder") { + } elsif ($self->is_folder($objects->[$i]->[1]) == 0) { #Workspace must be a folder $self->_error("Cannot create ".$objects->[$i]->[0]." because top level objects must be folders!"); } @@ -650,7 +661,7 @@ sub _validate_save_objects_before_saving { my $nocreate = 0; if (defined($obj)) { if ($obj->{folder} == 1) { - if ($objects->[$i]->[1] eq "folder") { + if ($self->is_folder($objects->[$i]->[1]) == 1) { #We ignore creation of folders that already exist $nocreate = 1; } else { @@ -807,6 +818,10 @@ sub _create_workspace { my ($self,$specs) = @_; if (!defined($specs->{user}) || length($specs->{user}) == 0) {$self->_error("Owner not specified in creation!");} if (!defined($specs->{workspace}) || length($specs->{workspace}) == 0) {$self->_error("Top directory not specified in creation!");} + if ($specs->{user} ne $self->_getUsername() && $self->_adminmode() == 0) { + $self->_error("User does not have permission to create workspace!"); + } + #Creating workspace directory on disk File::Path::mkpath ($self->_db_path()."/".$specs->{user}."/".$specs->{workspace}); #Creating workspace object in mongodb @@ -845,6 +860,7 @@ sub _create_object { $specs->{creation_date} = DateTime->now()->datetime(); } my $wsobj = $self->_wscache($specs->{user},$specs->{workspace}); + $self->_check_ws_permissions($wsobj,"w",1); my $object = { wsobj => $wsobj, size => 0, @@ -866,7 +882,7 @@ sub _create_object { if (!defined($object->{wsobj}->{owner}) || length($object->{wsobj}->{owner}) == 0) {$self->_error("Owner not specified in creation!");} if (!defined($object->{wsobj}->{name}) || length($object->{wsobj}->{name}) == 0) {$self->_error("Top directory not specified in creation!");} if (!defined($object->{name}) || length($object->{name}) == 0) {$self->_error("Name not specified in creation!");} - if ($specs->{type} eq "folder") { + if ($self->is_folder($specs->{type}) == 1) { #Creating folder on file system $object->{autometadata} = {}; $object->{folder} = 1; @@ -950,19 +966,63 @@ sub _wscache { #List all workspaces matching input query** sub _list_workspaces { - my ($self,$user) = @_; - my $query = { '$or' => [ {owner => $self->_getUsername()},{global_permission => {'$ne' => "n"} },{"permissions.".$self->_getUsername() => {'$exists' => 1 } } ] }; - if ($self->_adminmode() == 1) { - $query = {}; - } + my ($self,$user,$query) = @_; if (defined($user)) { - if ($user eq $self->_getUsername()) { - $query = {owner => $self->_getUsername()}; - } else { - $query = { '$and' => [ {owner => $user },{ '$or' => [ {global_permission => {'$ne' => "n"} },{"permissions.".$self->_getUsername() => {'$exists' => 1 } } ] } ] }; - if ($self->_adminmode() == 1) { - $query = {owner => $user}; + $query->{owner} = $user; + } + if ($self->_adminmode() != 1) { + if (defined($query->{'$or'})) { + my $oldarray = $query->{'$or'}; + $query->{'$or'} = []; + for (my $i=0; $i < @{$oldarray}; $i++) { + my $included = 0; + if (!defined($oldarray->[$i]->{owner})) { + my $hash = {}; + foreach my $key (keys(%{$oldarray->[$i]})) { + $hash->{$key} = $oldarray->[$i]->{$key}; + } + $hash->{owner} = $self->_getUsername(); + push(@{$query->{'$or'}},$hash); + } elsif ($oldarray->[$i]->{owner} eq $self->_getUsername()) { + $included = 1; + my $hash = {}; + foreach my $key (keys(%{$oldarray->[$i]})) { + $hash->{$key} = $oldarray->[$i]->{$key}; + } + push(@{$query->{'$or'}},$hash); + } + if (!defined($oldarray->[$i]->{global_permission})) { + my $hash = {}; + foreach my $key (keys(%{$oldarray->[$i]})) { + $hash->{$key} = $oldarray->[$i]->{$key}; + } + $hash->{global_permission} = {'$ne' => "n"}; + push(@{$query->{'$or'}},$hash); + } elsif ($oldarray->[$i]->{global_permission} ne "n" && $included == 0) { + $included = 1; + my $hash = {}; + foreach my $key (keys(%{$oldarray->[$i]})) { + $hash->{$key} = $oldarray->[$i]->{$key}; + } + push(@{$query->{'$or'}},$hash); + } + if (!defined($oldarray->[$i]->{"permissions.".$self->_getUsername()})) { + my $hash = {}; + foreach my $key (keys(%{$oldarray->[$i]})) { + $hash->{$key} = $oldarray->[$i]->{$key}; + } + $hash->{"permissions.".$self->_getUsername()} = {'$exists' => 1 }; + push(@{$query->{'$or'}},$hash); + } elsif ($included == 0) { + my $hash = {}; + foreach my $key (keys(%{$oldarray->[$i]})) { + $hash->{$key} = $oldarray->[$i]->{$key}; + } + push(@{$query->{'$or'}},$hash); + } } + } else { + $query->{'$or'} = [ {owner => $self->_getUsername()},{global_permission => {'$ne' => "n"}},{"permissions.".$self->_getUsername() => {'$exists' => 1 }} ] } } my $objs = []; @@ -1032,7 +1092,7 @@ sub _formatQuery { } } foreach my $term (keys(%{$inquery})) { - if (ref($inquery->{$term}) eq "ARRAY") { + if (ref($inquery->{$term}) eq "ARRAY" && $term ne '$or') { $inquery->{$term} = {'$in' => $inquery->{$term}}; } } @@ -1133,8 +1193,13 @@ sub _download_request my $url = $res->{shock_node} . "?download"; print STDERR "retrieve $url\n"; + my @headers; + if ($res->{user_token}) + { + @headers = (headers => {Authorization => "OAuth $res->{user_token}" }); + } http_request(GET => $url, - headers => {Authorization => "OAuth $res->{user_token}" }, + @headers, # handle_params => { max_read_size => 32768 }, on_body => sub { my($data, $hdr) = @_; @@ -1142,7 +1207,6 @@ sub _download_request { $writer->write($data); my $len = length($data); - print STDERR "B $len\n"; return 1; } else @@ -1191,7 +1255,6 @@ sub _download_request if ($h->{rbuf}) { my $len = length($h->{rbuf}); - print "R $len\n"; $writer->write($h->{rbuf}); $h->rbuf = ''; } @@ -1249,6 +1312,14 @@ sub _compute_autometadata { } }; +sub is_folder { + my($self, $type) = @_; + if (defined($self->{_foldertypes}->{lc($type)})) { + return 1; + } + return 0; +} + #END_HEADER sub new @@ -1288,7 +1359,7 @@ sub new $c->read($e); for my $p (@{$paramlist}) { my $v = $c->param("$service.$p"); - if ($v && !defined($params->{$p})) { + if ($v && !defined($params->{$p})) { $params->{$p} = $v; if ($v eq "null") { $params->{$p} = undef; @@ -1296,7 +1367,7 @@ sub new } } } - } + } $params = $self->_validateargs($params,["db-path","wsuser","wspassword","types-file"],{ "script-path" => "/kb/deployment/plbin/", "job-directory" => "/tmp/wsjobs/", @@ -1340,6 +1411,10 @@ sub new $self->{_params} = $params; $self->{_params}->{"db-path"} =~ s/\/\//\//g; $self->{_params}->{"db-path"} =~ s/\/$//g; + $self->{_foldertypes} = { + folder => 1, + modelfolder => 1 + }; #END_CONSTRUCTOR if ($self->can('_init_instance')) @@ -1849,7 +1924,7 @@ sub get } else { push(@{$output},[ $self->_generate_object_meta($obj), - $self->_retrieve_object_data($obj) + $self->_retrieve_object_data($obj,$wsobj) ]); } } @@ -2106,7 +2181,6 @@ sub get_download_url workspace_path => $ws_path, }; - print Dumper($obj); if (!defined($obj->{shock}) || $obj->{shock} == 0) { my $filename = $self->_db_path()."/".$obj->{wsobj}->{owner}."/".$obj->{wsobj}->{name}."/".$obj->{path}."/".$obj->{name}; $doc->{file_path} = $filename; @@ -2724,7 +2798,9 @@ sub delete
 $input is a set_permissions_params
-$output is an ObjectMeta
+$output is a reference to a list where each element is a reference to a list containing 2 items:
+	0: a Username
+	1: a WorkspacePerm
 set_permissions_params is a reference to a hash where the following keys are defined:
 	path has a value which is a FullObjectPath
 	permissions has a value which is a reference to a list where each element is a reference to a list containing 2 items:
@@ -2737,26 +2813,6 @@ FullObjectPath is a string
 Username is a string
 WorkspacePerm is a string
 bool is an int
-ObjectMeta is a reference to a list containing 12 items:
-	0: an ObjectName
-	1: an ObjectType
-	2: a FullObjectPath
-	3: (creation_time) a Timestamp
-	4: an ObjectID
-	5: (object_owner) a Username
-	6: an ObjectSize
-	7: a UserMetadata
-	8: an AutoMetadata
-	9: (user_permission) a WorkspacePerm
-	10: (global_permission) a WorkspacePerm
-	11: (shockurl) a string
-ObjectName is a string
-ObjectType is a string
-Timestamp is a string
-ObjectID is a string
-ObjectSize is an int
-UserMetadata is a reference to a hash where the key is a string and the value is a string
-AutoMetadata is a reference to a hash where the key is a string and the value is a string
 
 
@@ -2765,7 +2821,9 @@ AutoMetadata is a reference to a hash where the key is a string and the value is =begin text $input is a set_permissions_params -$output is an ObjectMeta +$output is a reference to a list where each element is a reference to a list containing 2 items: + 0: a Username + 1: a WorkspacePerm set_permissions_params is a reference to a hash where the following keys are defined: path has a value which is a FullObjectPath permissions has a value which is a reference to a list where each element is a reference to a list containing 2 items: @@ -2778,26 +2836,6 @@ FullObjectPath is a string Username is a string WorkspacePerm is a string bool is an int -ObjectMeta is a reference to a list containing 12 items: - 0: an ObjectName - 1: an ObjectType - 2: a FullObjectPath - 3: (creation_time) a Timestamp - 4: an ObjectID - 5: (object_owner) a Username - 6: an ObjectSize - 7: a UserMetadata - 8: an AutoMetadata - 9: (user_permission) a WorkspacePerm - 10: (global_permission) a WorkspacePerm - 11: (shockurl) a string -ObjectName is a string -ObjectType is a string -Timestamp is a string -ObjectID is a string -ObjectSize is an int -UserMetadata is a reference to a hash where the key is a string and the value is a string -AutoMetadata is a reference to a hash where the key is a string and the value is a string =end text @@ -2833,12 +2871,30 @@ sub set_permissions new_global_permission => undef }); my ($user,$ws,$path,$name) = $self->_parse_ws_path($input->{path}); + #Checking that workspace exists and a top lever directory is being adjusted my $wsobj = $self->_wscache($user,$ws,1); if (length($path) + length($name) > 0) { $self->_error("Can only set permissions on top-level folders!"); } - $self->_check_ws_permissions($wsobj,"a",1); + #Checking that user has permissions to change permissions + if ($wsobj->{global_permission} eq "p") { + if ($wsobj->{owner} ne $self->_getUsername() && $self->_adminmode() == 0) { + $self->_error("Only owner and administrators can change permissions on a published workspace!"); + } + } else { + $self->_check_ws_permissions($wsobj,"a",1); + } + #Checking that none of the user-permissions are "p" + for (my $i=0; $i < @{$input->{permissions}}; $i++) { + if ($input->{permissions}->[$i]->[1] eq "p") { + $self->_error("Cannot set user-specific permissions to publish!"); + } + } if (defined($input->{new_global_permission})) { + #Only workspace owner or administrator can set global permissions to "p" + if ($input->{new_global_permission} eq "p") { + $self->_check_ws_permissions($wsobj,"o",1); + } $input->{new_global_permission} = $self->_validate_workspace_permission($input->{new_global_permission}); $self->_updateDB("workspaces",{uuid => $wsobj->{uuid}},{'$set' => {global_permission => $input->{new_global_permission}}}); $wsobj->{global_permission} = $input->{new_global_permission}; @@ -2847,11 +2903,17 @@ sub set_permissions $input->{permissions}->[$i]->[1] = $self->_validate_workspace_permission($input->{permissions}->[$i]->[1]); if ($input->{permissions}->[$i]->[1] eq "n" && defined($wsobj->{permissions}->{$input->{permissions}->[$i]->[0]})) { $self->_updateDB("workspaces",{uuid => $wsobj->{uuid}},{'$unset' => {'permissions.'.$input->{permissions}->[$i]->[0] => $wsobj->{permissions}->{$input->{permissions}->[$i]->[0]}}}); + delete $wsobj->{permissions}->{$input->{permissions}->[$i]->[0]}; } else { $self->_updateDB("workspaces",{uuid => $wsobj->{uuid}},{'$set' => {'permissions.'.$input->{permissions}->[$i]->[0] => $input->{permissions}->[$i]->[1]}}); + $wsobj->{permissions}->{$input->{permissions}->[$i]->[0]} = $input->{permissions}->[$i]->[1]; } } - $output = $self->_generate_object_meta($wsobj); + $output = []; + foreach my $puser (keys(%{$wsobj->{permissions}})) { + push(@{$output},[$puser,$wsobj->{permissions}->{$puser}]); + } + push(@{$output},["global_permission",$wsobj->{global_permission}]); #END set_permissions my @_bad_returns; (ref($output) eq 'ARRAY') or push(@_bad_returns, "Invalid type for return variable \"output\" (value was \"$output\")"); @@ -2937,6 +2999,7 @@ sub list_permissions my($output); #BEGIN list_permissions $input = $self->_validateargs($input,["objects"],{}); + $output = {}; for (my $i=0; $i < @{$input->{objects}}; $i++) { my ($user,$ws,$path,$name) = $self->_parse_ws_path($input->{objects}->[$i]); my $wsobj = $self->_wscache($user,$ws,1); @@ -2944,6 +3007,7 @@ sub list_permissions foreach my $puser (keys(%{$wsobj->{permissions}})) { push(@{$output->{$input->{objects}->[$i]}},[$puser,$wsobj->{permissions}->{$puser}]); } + push(@{$output->{$input->{objects}->[$i]}},["global_permission",$wsobj->{global_permission}]); } #END list_permissions my @_bad_returns; diff --git a/lib/Bio/P3/Workspace/WorkspaceTests.pm b/lib/Bio/P3/Workspace/WorkspaceTests.pm index 1cd4a41..68714b4 100644 --- a/lib/Bio/P3/Workspace/WorkspaceTests.pm +++ b/lib/Bio/P3/Workspace/WorkspaceTests.pm @@ -7,10 +7,13 @@ use Data::Dumper; use Config::Simple; + my $serverclass = "Bio::P3::Workspace::WorkspaceImpl"; + my $clientclass = "Bio::P3::Workspace::WorkspaceClient"; + sub new { my($class,$bin) = @_; my $c = Config::Simple->new(); - $c->read($bin."test.cfg"); + $c->read($bin."/test.cfg"); my $self = { testcount => 0, dumpoutput => $c->param("WorkspaceTest.dumpoutput"), @@ -32,16 +35,21 @@ }); $ENV{KB_INTERACTIVE} = 1; if (defined($c->param("WorkspaceTest.serverconfig"))) { - $ENV{KB_DEPLOYMENT_CONFIG} = $bin.$c->param("WorkspaceTest.serverconfig"); + $ENV{KB_DEPLOYMENT_CONFIG} = $bin."/".$c->param("WorkspaceTest.serverconfig"); } if (!defined($self->{url}) || $self->{url} eq "impl") { print "Loading server with this config: ".$ENV{KB_DEPLOYMENT_CONFIG}."\n"; - require "Bio/P3/Workspace/WorkspaceImpl.pm"; - $self->{obj} = Bio::P3::Workspace::WorkspaceImpl->new(); + my $classpath = $serverclass; + $classpath =~ s/::/\//g; + require $classpath.".pm"; + $self->{obj} = $serverclass->new({}); } else { - require "Bio/P3/Workspace/WorkspaceClient.pm"; - $self->{clientobj} = Bio::P3::Workspace::WorkspaceClient->new($self->{url},token => $self->{token}); - $self->{clientobjtwo} = Bio::P3::Workspace::WorkspaceClient->new($self->{url},token => $self->{tokentwo}); + my $classpath = $clientclass; + $classpath =~ s/::/\//g; + require $classpath.".pm"; + $self->{clientobj} = $clientclass->new($self->{url},token => $self->{token}); + $self->{clientobjtwo} = $clientclass->new($self->{url},token => $self->{tokentwo}); + $self->{clientobjthree} = $clientclass->new($self->{url},token => undef); } return bless $self, $class; } @@ -50,13 +58,20 @@ my($self,$user) = @_; if (!defined($self->{url}) || $self->{url} eq "impl") { if ($user == 2) { - $Bio::P3::Workspace::WorkspaceImpl::CallContext = Bio::P3::Workspace::WorkspaceImpl::CallContext->new($self->{tokentwo},"test",$self->{usertwo}); + $Bio::P3::Workspace::WorkspaceImpl::CallContext = undef; + $Bio::P3::Workspace::WorkspaceImpl::CallContext = CallContext->new($self->{tokentwo},"test",$self->{usertwo}); + } elsif ($user == 3) { + $Bio::P3::Workspace::WorkspaceImpl::CallContext = undef; + $Bio::P3::Workspace::WorkspaceImpl::CallContext = CallContext->new(undef,"test",undef); } else { - $Bio::P3::Workspace::WorkspaceImpl::CallContext = Bio::P3::Workspace::WorkspaceImpl::CallContext->new($self->{token},"test",$self->{user}); + $Bio::P3::Workspace::WorkspaceImpl::CallContext = undef; + $Bio::P3::Workspace::WorkspaceImpl::CallContext = CallContext->new($self->{token},"test",$self->{user}); } } else { if ($user == 2) { $self->{obj} = $self->{clientobjtwo}; + } elsif ($user == 3) { + $self->{obj} = $self->{clientobjthree}; } else { $self->{obj} = $self->{clientobj}; } @@ -298,11 +313,9 @@ ["defined(\$output->[0])","Getting metadata for created object back"], ["\$output->[0]->[1] eq \"genome\"","Object has type genome"], ["defined(\$output->[0]->[11])","Shock URL is returned"] - ],0,undef,1); #Uploading file to newly created shock node - print "Filename:".$self->{bin}."testdata.txt\n"; my $req = HTTP::Request::Common::POST($output->[0]->[11],Authorization => "OAuth ".$self->{token},Content_Type => 'multipart/form-data',Content => [upload => [$self->{bin}."testdata.txt"]]); $req->method('PUT'); my $ua = LWP::UserAgent->new(); @@ -416,9 +429,15 @@ new_global_permission => "w" },"Successfully changed global permissions!",[],0,undef,1); + #Copy objects + $output = $self->test_harness("copy",{ + objects => [["/".$self->{usertwo}."/TestWorkspace/copydir","/".$self->{usertwo}."/TestWorkspace/copydir_two"]], + recursive => 1 + },"Successfully ran copy to move objects!",[],0,undef,2); + #Moving objects $output = $self->test_harness("copy",{ - objects => [["/".$self->{usertwo}."/TestWorkspace/copydir","/".$self->{user}."/TestWorkspace/movedir"]], + objects => [["/".$self->{usertwo}."/TestWorkspace/copydir_two","/".$self->{user}."/TestWorkspace/movedir"]], recursive => 1, move => 1 },"Successfully ran copy to move objects!",[],0,undef,2); @@ -446,6 +465,20 @@ objects => [["/".$self->{user}."/TestWorkspace/emptydir","folder",{},undef]] },"Successfully created a workspace directory!",[],0,undef,1); + #Creating a model folder + $output = $self->test_harness("create",{ + objects => [["/".$self->{user}."/TestWorkspace/emptydir/modelfolder","modelfolder",{},undef]] + },"Successfully created a folder with type other than folder!",[],0,undef,1); + + #Listing model folder + $output = $self->test_harness("ls",{ + paths => ["/".$self->{user}."/TestWorkspace/emptydir/"], + excludeDirectories => 0, + excludeObjects => 0, + recursive => 1 + },"Listing nonfolder directory",[ + ["\$output->{\"/".$self->{user}."/TestWorkspace/emptydir/\"}->[0]->[8]->{is_folder} == 1","ls indicates that object is a folder"] + ],0,undef,1); #Getting an object $output = $self->test_harness("get",{ objects => ["/".$self->{user}."/TestWorkspace/testdir/testdir2/testdir3/testobj"] @@ -460,6 +493,45 @@ ["defined(\$output->[0]->[1])","Object retrieved with all data"] ],0,undef,1); + #Publishing workspace + $output = $self->test_harness("set_permissions",{ + path => "/".$self->{usertwo}."/TestWorkspace", + new_global_permission => "p" + },"Publishing workspace",[],0,undef,2); + + #Attempting to write to published workspace + $output = $self->test_harness("create",{ + objects => [["/".$self->{usertwo}."/TestWorkspace/test_write_published_workspace","folder",{},undef]] + },"Cannot write to published workspace",[],1,undef,2); + + #Listing public workspace + $output = $self->test_harness("ls",{ + paths => ["/".$self->{usertwo}."/TestWorkspace"] + },"Using ls function on published workspace without auth",[],0,undef,3); + + #Getting object from public workspace + $output = $self->test_harness("get",{ + objects => ["/".$self->{usertwo}."/TestWorkspace/copydir/testdir2/testdir3/shockobj"] + },"Using get function to retrieve object from published workspace without auth",[],0,undef,3); + system("curl ".$output->[0]->[0]->[11]."?download"); + + #Unpublishing workspace + $output = $self->test_harness("set_permissions",{ + path => "/".$self->{usertwo}."/TestWorkspace", + new_global_permission => "r" + },"Unpublishing workspace",[],0,undef,2); + + #Listing public workspace + $output = $self->test_harness("ls",{ + paths => ["/".$self->{usertwo}."/TestWorkspace"] + },"Using ls function on public workspace without auth",[],0,undef,3); + + #Getting object from public workspace + $output = $self->test_harness("get",{ + objects => ["/".$self->{usertwo}."/TestWorkspace/copydir/testdir2/testdir3/shockobj"] + },"Using get function to retrieve object from public workspace without auth",[],0,undef,3); + system("curl ".$output->[0]->[0]->[11]."?download"); + #Deleting workspaces $output = $self->test_harness("delete",{ objects => ["/".$self->{user}."/TestWorkspace"], @@ -478,7 +550,7 @@ } { - package Bio::P3::Workspace::WorkspaceImpl::CallContext; + package CallContext; use strict; diff --git a/lib/WorkspaceServer.py b/lib/WorkspaceServer.py index 2fc0363..0949910 100644 --- a/lib/WorkspaceServer.py +++ b/lib/WorkspaceServer.py @@ -275,7 +275,7 @@ def __init__(self): self.rpc_service.add(impl_Workspace.get, name='Workspace.get', types=[dict]) - self.method_authentication['Workspace.get'] = 'required' + self.method_authentication['Workspace.get'] = 'optional' self.rpc_service.add(impl_Workspace.update_auto_meta, name='Workspace.update_auto_meta', types=[dict]) @@ -283,15 +283,15 @@ def __init__(self): self.rpc_service.add(impl_Workspace.get_download_url, name='Workspace.get_download_url', types=[dict]) - self.method_authentication['Workspace.get_download_url'] = 'required' + self.method_authentication['Workspace.get_download_url'] = 'optional' self.rpc_service.add(impl_Workspace.get_archive_url, name='Workspace.get_archive_url', types=[dict]) - self.method_authentication['Workspace.get_archive_url'] = 'none' + self.method_authentication['Workspace.get_archive_url'] = 'optional' self.rpc_service.add(impl_Workspace.ls, name='Workspace.ls', types=[dict]) - self.method_authentication['Workspace.ls'] = 'required' + self.method_authentication['Workspace.ls'] = 'optional' self.rpc_service.add(impl_Workspace.copy, name='Workspace.copy', types=[dict]) @@ -307,7 +307,7 @@ def __init__(self): self.rpc_service.add(impl_Workspace.list_permissions, name='Workspace.list_permissions', types=[dict]) - self.method_authentication['Workspace.list_permissions'] = 'required' + self.method_authentication['Workspace.list_permissions'] = 'optional' self.auth_client = biokbase.nexus.Client( config={'server': 'nexus.api.globusonline.org', 'verify_ssl': True, diff --git a/scripts/ws-create.pl b/scripts/ws-create.pl index 706ca04..56d3033 100644 --- a/scripts/ws-create.pl +++ b/scripts/ws-create.pl @@ -22,7 +22,7 @@ =head1 COMMAND-LINE OPTIONS =cut my($opt, $usage) = Bio::P3::Workspace::ScriptHelpers::options("%c %o ",[ - ["permission|p", "Permissions for folders created"], + ["permission|p=s", "Permissions for folders created"], ["useshock|u", "Upload file to shock and store link in workspace"], ["overwrite|o", "Overwirte existing destination object"], ]); @@ -57,6 +57,7 @@ =head1 COMMAND-LINE OPTIONS Content => [upload => [$filename]]); $req->method('PUT'); my $sres = $ua->request($req); + print Data::Dumper->Dump([$sres]); } print "File created:\n"; diff --git a/scripts/ws-ls.pl b/scripts/ws-ls.pl index 9cda031..7a0fc75 100644 --- a/scripts/ws-ls.pl +++ b/scripts/ws-ls.pl @@ -27,6 +27,9 @@ =head1 COMMAND-LINE OPTIONS ["recursive|r", "Recursively list subdirectory contents"], ["shock|s", "Include shock URLs"], ]); +if (!defined($ARGV[0])) { + $ARGV[0] = ""; +} my $paths = Bio::P3::Workspace::ScriptHelpers::process_paths([@ARGV]); my $res = Bio::P3::Workspace::ScriptHelpers::wscall("ls",{ paths => $paths, diff --git a/scripts/ws-perms.pl b/scripts/ws-perms.pl new file mode 100644 index 0000000..0e55bd4 --- /dev/null +++ b/scripts/ws-perms.pl @@ -0,0 +1,47 @@ + +use strict; +use Bio::P3::Workspace::ScriptHelpers; +=head1 NAME + +ws-create + +=head1 SYNOPSIS + +ws-create ws-name + +=head1 DESCRIPTION + +Create a workspace + +=head1 COMMAND-LINE OPTIONS + +ws-create workspace [long options...] + --url URL to use for workspace service + --help print usage message and exit + +=cut + +my($opt, $usage) = Bio::P3::Workspace::ScriptHelpers::options("%c %o ",[ + ["perm|p=s", "permission to reset globally or for specific users"], + ["users|u=s", "; delimited list of users to set permissions for"], +]); +my $paths = Bio::P3::Workspace::ScriptHelpers::process_paths([$ARGV[0]]); +if (defined($opt->{perm})) { + my $perms = $opt->{perm}; + my $input = { + path => $paths->[0] + }; + if (defined($opt->{users})) { + my $array = [split(/;/,$opt->{users})]; + for (my $i=0; $i < @{$array}; $i++) { + push(@{$input->{permissions}},[$array->[$i],$perms]); + } + } else { + $input->{new_global_permission} = $perms; + } + my $res = Bio::P3::Workspace::ScriptHelpers::wscall("set_permissions",$input); + print Data::Dumper->Dump([$res]); +} else { + my $res = Bio::P3::Workspace::ScriptHelpers::wscall("list_permissions",{objects => $paths}); + print Data::Dumper->Dump([$res->{$paths->[0]}]); +} \ No newline at end of file diff --git a/t/client-tests/tests.t b/t/client-tests/tests.t index 1bbcdb1..f309c2b 100644 --- a/t/client-tests/tests.t +++ b/t/client-tests/tests.t @@ -1,5 +1,5 @@ use FindBin qw($Bin); use Bio::P3::Workspace::WorkspaceTests; -my $tester = Bio::P3::Workspace::WorkspaceTests->new($bin); +my $tester = Bio::P3::Workspace::WorkspaceTests->new($Bin); $tester->run_tests(); \ No newline at end of file diff --git a/t/server-tests/tests.t b/t/server-tests/tests.t index 1bbcdb1..f309c2b 100644 --- a/t/server-tests/tests.t +++ b/t/server-tests/tests.t @@ -1,5 +1,5 @@ use FindBin qw($Bin); use Bio::P3::Workspace::WorkspaceTests; -my $tester = Bio::P3::Workspace::WorkspaceTests->new($bin); +my $tester = Bio::P3::Workspace::WorkspaceTests->new($Bin); $tester->run_tests(); \ No newline at end of file diff --git a/typeslist.txt b/typeslist.txt index 5b2bb19..90e6f2b 100644 --- a/typeslist.txt +++ b/typeslist.txt @@ -33,6 +33,7 @@ json mapping media model +modelfolder model_edit modeltemplate pdf @@ -54,3 +55,4 @@ wig xls xlsx zip +contigset