uglifier (~> 2.0)
BUNDLED WITH
- 2.0.2
+ 1.17.3
def show_pane_list
if current_user.andand.is_admin
- super | %w(Admin)
+ %w(Admin) | super
else
super
end
<div class="modal-header">
<button type="button" class="close" onClick="reset_form()" data-dismiss="modal" aria-hidden="true">×</button>
<div>
- <div class="col-sm-6"> <h4 class="modal-title">Setup Shell Account</h4> </div>
+ <div class="col-sm-6"> <h4 class="modal-title">Setup Account</h4> </div>
<div class="spinner spinner-32px spinner-h-center col-sm-1" hidden="true"></div>
</div>
<br/>
<% end %>
</div>
<div class="form-group">
- <label for="vm_uuid">Virtual Machine</label>
+ <label for="vm_uuid">Virtual Machine (optional)</label>
<select class="form-control" name="vm_uuid">
<option value="" <%= 'selected' unless selected_vm %>>
Choose One:
</select>
</div>
<div class="groups-group">
- <label for="groups">Groups for virtual machine (comma separated list)</label>
+ <label for="groups">Groups for virtual machine (comma separated list) (optional)</label>
<input class="form-control" id="groups" maxlength="250" name="groups" type="text" value="<%=groups%>">
</div>
</div>
<div class="row">
<div class="col-md-6">
+
<p>
- As an admin, you can log in as this user. When you’ve
- finished, you will need to log out and log in again with your
- own account.
+ This page enables you to <a href="https://doc.arvados.org/master/admin/user-management.html">manage users</a>.
</p>
- <blockquote>
- <%= button_to "Log in as #{@object.full_name}", sudo_user_url(id: @object.uuid), class: 'btn btn-primary' %>
- </blockquote>
-
<p>
- As an admin, you can setup a shell account for this user.
- The login name is automatically generated from the user's e-mail address.
+ This button sets up a user. After setup, they will be able use
+ Arvados. This dialog box also allows you to optionally set up a
+ shell account for this user. The login name is automatically
+ generated from the user's e-mail address.
</p>
- <blockquote>
- <%= link_to "Setup shell account #{'for ' if @object.full_name.present?} #{@object.full_name}", setup_popup_user_url(id: @object.uuid), {class: 'btn btn-primary', :remote => true, 'data-toggle' => "modal", 'data-target' => '#user-setup-modal-window'} %>
- </blockquote>
+ <%= link_to "Setup account #{'for ' if @object.full_name.present?} #{@object.full_name}", setup_popup_user_url(id: @object.uuid), {class: 'btn btn-primary', :remote => true, 'data-toggle' => "modal", 'data-target' => '#user-setup-modal-window'} %>
- <p>
+ <p style="margin-top: 3em">
As an admin, you can deactivate and reset this user. This will
remove all repository/VM permissions for the user. If you
"setup" the user again, the user will have to sign the user
- agreement again.
+ agreement again. You may also want to <a href="https://doc.arvados.org/master/admin/reassign-ownership.html">reassign data ownership</a>.
+ </p>
+
+ <%= button_to "Deactivate #{@object.full_name}", unsetup_user_url(id: @object.uuid), class: 'btn btn-primary', data: {confirm: "Are you sure you want to deactivate #{@object.full_name}?"} %>
+
+ <p style="margin-top: 3em">
+ As an admin, you can log in as this user. When you’ve
+ finished, you will need to log out and log in again with your
+ own account.
</p>
- <blockquote>
- <%= button_to "Deactivate #{@object.full_name}", unsetup_user_url(id: @object.uuid), class: 'btn btn-primary', data: {confirm: "Are you sure you want to deactivate #{@object.full_name}?"} %>
- </blockquote>
+ <%= button_to "Log in as #{@object.full_name}", sudo_user_url(id: @object.uuid), class: 'btn btn-primary' %>
</div>
<div class="col-md-6">
<div class="panel panel-default">
find('a', text: 'Show').
click
+ click_link 'Attributes'
+
assert page.has_text? 'modified_by_user_uuid'
page.within(:xpath, '//span[@data-name="is_active"]') do
assert_equal "false", text, "Expected new user's is_active to be false"
click_link 'Advanced'
click_link 'Metadata'
- assert page.has_text? 'can_login' # make sure page is rendered / ready
+ assert page.has_text? 'can_read' # make sure page is rendered / ready
assert page.has_no_text? 'VirtualMachine:'
end
# Setup user
click_link 'Admin'
- assert page.has_text? 'As an admin, you can setup'
+ assert page.has_text? 'This button sets up a user'
- click_link 'Setup shell account for Active User'
+ click_link 'Setup account for Active User'
within '.modal-content' do
find 'label', text: 'Virtual Machine'
end
visit user_url
+ click_link 'Attributes'
assert page.has_text? 'modified_by_client_uuid'
click_link 'Advanced'
# Click on Setup button again and this time also choose a VM
click_link 'Admin'
- click_link 'Setup shell account for Active User'
+ click_link 'Setup account for Active User'
within '.modal-content' do
select("testvm.shell", :from => 'vm_uuid')
end
visit user_url
+ click_link 'Attributes'
find '#Attributes', text: 'modified_by_client_uuid'
click_link 'Advanced'
click_link 'Metadata'
assert page.has_text? 'VirtualMachine: testvm.shell'
assert page.has_text? '["test group one", "test-group-two"]'
+ vm_links = all("a", text: "VirtualMachine:")
+ assert_equal(2, vm_links.size)
end
test "unsetup active user" do
user_url = page.current_url
# Verify that is_active is set
- find('a,button', text: 'Attributes').click
+ click_link 'Attributes'
assert page.has_text? 'modified_by_user_uuid'
page.within(:xpath, '//span[@data-name="is_active"]') do
assert_equal "true", text, "Expected user's is_active to be true"
# poltergeist returns true for confirm(), so we don't need to accept.
end
+ click_link 'Attributes'
+
# Should now be back in the Attributes tab for the user
assert page.has_text? 'modified_by_user_uuid'
page.within(:xpath, '//span[@data-name="is_active"]') do
# setup user again and verify links present
click_link 'Admin'
- click_link 'Setup shell account for Active User'
+ click_link 'Setup account for Active User'
within '.modal-content' do
select("testvm.shell", :from => 'vm_uuid')
end
visit user_url
+ click_link 'Attributes'
assert page.has_text? 'modified_by_client_uuid'
click_link 'Advanced'
# Setup user
click_link 'Admin'
- assert page.has_text? 'As an admin, you can setup'
+ assert page.has_text? 'This button sets up a user'
click_link 'Add new group'
n += 1
raise if n > 2 || e.is_a?(Skip)
STDERR.puts "Test failed, retrying (##{n})"
+ ActiveSupport::TestCase.reset_api_fixtures_now
retry
end
rescue *PASSTHROUGH_EXCEPTIONS
test -d ubuntu1804/generated || mkdir ubuntu1804/generated
cp -rlt ubuntu1804/generated common-generated/*
-GOTARBALL=go1.12.7.linux-amd64.tar.gz
+GOTARBALL=go1.13.4.linux-amd64.tar.gz
NODETARBALL=node-v6.11.2-linux-x64.tar.xz
RVMKEY1=mpapis.asc
RVMKEY2=pkuczynski.asc
curl -L https://get.rvm.io | bash -s stable && \
/usr/local/rvm/bin/rvm install 2.5 && \
/usr/local/rvm/bin/rvm alias create default ruby-2.5 && \
+ /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.0.2 && \
/usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
# Install Bash 4.4.12 // see https://dev.arvados.org/issues/15612
&& ln -sf /usr/local/src/bash-4.4.12/bash /bin/bash
# Install golang binary
-ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
curl -L https://get.rvm.io | bash -s stable && \
/usr/local/rvm/bin/rvm install 2.5 && \
/usr/local/rvm/bin/rvm alias create default ruby-2.5 && \
+ /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.0.2 && \
/usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
# Install golang binary
-ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
curl -L https://get.rvm.io | bash -s stable && \
/usr/local/rvm/bin/rvm install 2.5 && \
/usr/local/rvm/bin/rvm alias create default ruby-2.5 && \
+ /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.0.2 && \
/usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
# Install golang binary
-ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
curl -L https://get.rvm.io | bash -s stable && \
/usr/local/rvm/bin/rvm install 2.5 && \
/usr/local/rvm/bin/rvm alias create default ruby-2.5 && \
+ /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.0.2 && \
/usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
# Install golang binary
-ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
curl -L https://get.rvm.io | bash -s stable && \
/usr/local/rvm/bin/rvm install 2.5 && \
/usr/local/rvm/bin/rvm alias create default ruby-2.5 && \
+ /usr/local/rvm/bin/rvm-exec default gem install bundler --version 2.0.2 && \
/usr/local/rvm/bin/rvm-exec default gem install fpm --version 1.10.2
# Install golang binary
-ADD generated/go1.12.7.linux-amd64.tar.gz /usr/local/
+ADD generated/go1.13.4.linux-amd64.tar.gz /usr/local/
RUN ln -s /usr/local/go/bin/go /usr/local/bin/
# Install nodejs and npm
To access a monitoring endpoint, the requester must provide the HTTP header @Authorization: Bearer (ManagementToken)@.
-h2. API server
-
-Set @ManagementToken@ in the appropriate section of @application.yml@
-
-<pre>
-production:
- # Token to be included in all healthcheck requests. Disabled by default.
- # Server expects request header of the format "Authorization: Bearer xxx"
- ManagementToken: xxx
-</pre>
-
h2. Node Manager
Set @port@ (the listen port) and @ManagementToken@ in the @Manage@ section of @node-manager.ini@.
ManagementToken = xxx
</pre>
-h2. Other services
+h2. API server and other services
-The following services also support monitoring. Set @ManagementToken@ in the respective yaml config file for each service.
+The following services also support monitoring.
+* API server
+* arv-git-httpd
+* controller
+* keep-balance
+* keepproxy
* keepstore
* keep-web
-* keepproxy
-* arv-git-httpd
* websockets
+
+Set @ManagementToken@ in the appropriate section of @/etc/arvados/config.yml@.
+
+<notextile>
+<pre><code>Clusters:
+ <span class="userinput">uuid_prefix</span>:
+ # Token to be included in all healthcheck requests. Disabled by default.
+ # Server expects request header of the format "Authorization: Bearer xxx"
+ ManagementToken: xxx
+</code></pre>
+</notextile>
"os"
"os/exec"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/ctxlog"
"github.com/ghodss/yaml"
"github.com/sirupsen/logrus"
if err != nil {
return 1
}
+ problems := false
+ if warnAboutProblems(logger, withDepr) {
+ problems = true
+ }
cmd := exec.Command("diff", "-u", "--label", "without-deprecated-configs", "--label", "relying-on-deprecated-configs", "/dev/fd/3", "/dev/fd/4")
for _, obj := range []interface{}{withoutDepr, withDepr} {
y, _ := yaml.Marshal(obj)
if logbuf.Len() > 0 {
return 1
}
- return 0
+
+ if problems {
+ return 1
+ } else {
+ return 0
+ }
+}
+
+func warnAboutProblems(logger logrus.FieldLogger, cfg *arvados.Config) bool {
+ warned := false
+ for id, cc := range cfg.Clusters {
+ if cc.SystemRootToken == "" {
+ logger.Warnf("Clusters.%s.SystemRootToken is empty; see https://doc.arvados.org/master/install/install-keepstore.html", id)
+ warned = true
+ }
+ if cc.ManagementToken == "" {
+ logger.Warnf("Clusters.%s.ManagementToken is empty; see https://doc.arvados.org/admin/management-token.html", id)
+ warned = true
+ }
+ }
+ return warned
}
var DumpDefaultsCommand defaultsCommand
os.Unsetenv("ARVADOS_API_TOKEN")
}
-func (s *CommandSuite) TestBadArg(c *check.C) {
+func (s *CommandSuite) TestDump_BadArg(c *check.C) {
var stderr bytes.Buffer
code := DumpCommand.RunCommand("arvados config-dump", []string{"-badarg"}, bytes.NewBuffer(nil), bytes.NewBuffer(nil), &stderr)
c.Check(code, check.Equals, 2)
c.Check(stderr.String(), check.Matches, `(?ms)flag provided but not defined: -badarg\nUsage:\n.*`)
}
-func (s *CommandSuite) TestEmptyInput(c *check.C) {
+func (s *CommandSuite) TestDump_EmptyInput(c *check.C) {
var stdout, stderr bytes.Buffer
code := DumpCommand.RunCommand("arvados config-dump", []string{"-config", "-"}, &bytes.Buffer{}, &stdout, &stderr)
c.Check(code, check.Equals, 1)
c.Check(stderr.String(), check.Matches, `config does not define any clusters\n`)
}
-func (s *CommandSuite) TestCheckNoDeprecatedKeys(c *check.C) {
+func (s *CommandSuite) TestCheck_NoWarnings(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
z1234:
+ ManagementToken: xyzzy
+ SystemRootToken: xyzzy
API:
MaxItemsPerResponse: 1234
PostgreSQL:
c.Check(stderr.String(), check.Equals, "")
}
-func (s *CommandSuite) TestCheckDeprecatedKeys(c *check.C) {
+func (s *CommandSuite) TestCheck_DeprecatedKeys(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
c.Check(stdout.String(), check.Matches, `(?ms).*\n\- +.*MaxItemsPerResponse: 1000\n\+ +MaxItemsPerResponse: 1234\n.*`)
}
-func (s *CommandSuite) TestCheckOldKeepstoreConfigFile(c *check.C) {
+func (s *CommandSuite) TestCheck_OldKeepstoreConfigFile(c *check.C) {
f, err := ioutil.TempFile("", "")
c.Assert(err, check.IsNil)
defer os.Remove(f.Name())
c.Check(stderr.String(), check.Matches, `(?ms).*you should remove the legacy keepstore config file.*\n`)
}
-func (s *CommandSuite) TestCheckUnknownKey(c *check.C) {
+func (s *CommandSuite) TestCheck_UnknownKey(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
c.Check(stderr.String(), check.Matches, `(?ms).*unexpected object in config entry: Clusters.z1234.PostgreSQL.ConnectionPool"\n.*`)
}
-func (s *CommandSuite) TestDumpFormatting(c *check.C) {
+func (s *CommandSuite) TestDump_Formatting(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
c.Check(stdout.String(), check.Matches, `(?ms).*http://localhost:12345: {}\n.*`)
}
-func (s *CommandSuite) TestDumpUnknownKey(c *check.C) {
+func (s *CommandSuite) TestDump_UnknownKey(c *check.C) {
var stdout, stderr bytes.Buffer
in := `
Clusters:
"net/url"
"regexp"
"strings"
+ "time"
"git.curoverse.com/arvados.git/lib/config"
"git.curoverse.com/arvados.git/lib/controller/localdb"
}
}
+func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+ return conn.generated_CollectionList(ctx, options)
+}
+
func (conn *Conn) CollectionProvenance(ctx context.Context, options arvados.GetOptions) (map[string]interface{}, error) {
return conn.chooseBackend(options.UUID).CollectionProvenance(ctx, options)
}
return conn.chooseBackend(options.UUID).CollectionUntrash(ctx, options)
}
+func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+ return conn.generated_ContainerList(ctx, options)
+}
+
func (conn *Conn) ContainerCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Container, error) {
return conn.chooseBackend(options.ClusterID).ContainerCreate(ctx, options)
}
return conn.chooseBackend(options.UUID).ContainerUnlock(ctx, options)
}
+func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+ return conn.generated_SpecimenList(ctx, options)
+}
+
func (conn *Conn) SpecimenCreate(ctx context.Context, options arvados.CreateOptions) (arvados.Specimen, error) {
return conn.chooseBackend(options.ClusterID).SpecimenCreate(ctx, options)
}
return conn.chooseBackend(options.UUID).SpecimenDelete(ctx, options)
}
+var userAttrsCachedFromLoginCluster = map[string]bool{
+ "created_at": true,
+ "email": true,
+ "first_name": true,
+ "is_active": true,
+ "is_admin": true,
+ "last_name": true,
+ "modified_at": true,
+ "modified_by_client_uuid": true,
+ "modified_by_user_uuid": true,
+ "prefs": true,
+ "username": true,
+
+ "full_name": false,
+ "identity_url": false,
+ "is_invited": false,
+ "owner_uuid": false,
+ "uuid": false,
+}
+
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ logger := ctxlog.FromContext(ctx)
+ if id := conn.cluster.Login.LoginCluster; id != "" && id != conn.cluster.ClusterID {
+ resp, err := conn.chooseBackend(id).UserList(ctx, options)
+ if err != nil {
+ return resp, err
+ }
+ batchOpts := arvados.UserBatchUpdateOptions{Updates: map[string]map[string]interface{}{}}
+ for _, user := range resp.Items {
+ if !strings.HasPrefix(user.UUID, id) {
+ continue
+ }
+ logger.Debugf("cache user info for uuid %q", user.UUID)
+
+ // If the remote cluster has null timestamps
+ // (e.g., test server with incomplete
+ // fixtures) use dummy timestamps (instead of
+ // the zero time, which causes a Rails API
+ // error "year too big to marshal: 1 UTC").
+ if user.ModifiedAt.IsZero() {
+ user.ModifiedAt = time.Now()
+ }
+ if user.CreatedAt.IsZero() {
+ user.CreatedAt = time.Now()
+ }
+
+ var allFields map[string]interface{}
+ buf, err := json.Marshal(user)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error encoding user record from remote response: %s", err)
+ }
+ err = json.Unmarshal(buf, &allFields)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error transcoding user record from remote response: %s", err)
+ }
+ updates := allFields
+ if len(options.Select) > 0 {
+ updates = map[string]interface{}{}
+ for _, k := range options.Select {
+ if v, ok := allFields[k]; ok && userAttrsCachedFromLoginCluster[k] {
+ updates[k] = v
+ }
+ }
+ } else {
+ for k := range updates {
+ if !userAttrsCachedFromLoginCluster[k] {
+ delete(updates, k)
+ }
+ }
+ }
+ batchOpts.Updates[user.UUID] = updates
+ }
+ if len(batchOpts.Updates) > 0 {
+ ctxRoot := auth.NewContext(ctx, &auth.Credentials{Tokens: []string{conn.cluster.SystemRootToken}})
+ _, err = conn.local.UserBatchUpdate(ctxRoot, batchOpts)
+ if err != nil {
+ return arvados.UserList{}, fmt.Errorf("error updating local user records: %s", err)
+ }
+ }
+ return resp, nil
+ } else {
+ return conn.generated_UserList(ctx, options)
+ }
+}
+
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.ClusterID).UserCreate(ctx, options)
+}
+
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUpdate(ctx, options)
+}
+
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUpdateUUID(ctx, options)
+}
+
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.OldUserUUID).UserMerge(ctx, options)
+}
+
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserActivate(ctx, options)
+}
+
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ return conn.chooseBackend(options.UUID).UserSetup(ctx, options)
+}
+
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserUnsetup(ctx, options)
+}
+
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGet(ctx, options)
+}
+
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGetCurrent(ctx, options)
+}
+
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserGetSystem(ctx, options)
+}
+
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ return conn.chooseBackend(options.UUID).UserDelete(ctx, options)
+}
+
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ return conn.local.UserBatchUpdate(ctx, options)
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
return conn.chooseBackend(options.UUID).APIClientAuthorizationCurrent(ctx, options)
}
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package federation
+
+import (
+ "context"
+ "net/url"
+ "os"
+ "testing"
+
+ "git.curoverse.com/arvados.git/lib/controller/router"
+ "git.curoverse.com/arvados.git/lib/controller/rpc"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
+ "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+ "git.curoverse.com/arvados.git/sdk/go/auth"
+ "git.curoverse.com/arvados.git/sdk/go/ctxlog"
+ "git.curoverse.com/arvados.git/sdk/go/httpserver"
+ check "gopkg.in/check.v1"
+)
+
+// Gocheck boilerplate
+func Test(t *testing.T) {
+ check.TestingT(t)
+}
+
+// FederationSuite does some generic setup/teardown. Don't add Test*
+// methods to FederationSuite itself.
+type FederationSuite struct {
+ cluster *arvados.Cluster
+ ctx context.Context
+ fed *Conn
+}
+
+func (s *FederationSuite) SetUpTest(c *check.C) {
+ s.cluster = &arvados.Cluster{
+ ClusterID: "aaaaa",
+ SystemRootToken: arvadostest.SystemRootToken,
+ RemoteClusters: map[string]arvados.RemoteCluster{
+ "aaaaa": arvados.RemoteCluster{
+ Host: os.Getenv("ARVADOS_API_HOST"),
+ },
+ },
+ }
+ arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
+ s.cluster.TLS.Insecure = true
+ s.cluster.API.MaxItemsPerResponse = 3
+
+ ctx := context.Background()
+ ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
+ ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
+ s.ctx = ctx
+
+ s.fed = New(s.cluster)
+}
+
+func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
+ s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
+ Host: "in-process.local",
+ }
+ s.fed.remotes[id] = backend
+}
+
+func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
+ srv := httpserver.Server{Addr: ":"}
+ srv.Handler = router.New(backend)
+ c.Check(srv.Start(), check.IsNil)
+ s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
+ Scheme: "http",
+ Host: srv.Addr,
+ Proxy: true,
+ }
+ s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
+}
if err != nil {
panic(err)
}
- orig := regexp.MustCompile(`(?ms)\nfunc [^\n]*CollectionList\(.*?\n}\n`).Find(buf)
+ orig := regexp.MustCompile(`(?ms)\nfunc [^\n]*generated_CollectionList\(.*?\n}\n`).Find(buf)
if len(orig) == 0 {
panic("can't find CollectionList func")
}
defer out.Close()
out.Write(regexp.MustCompile(`(?ms)^.*package .*?import.*?\n\)\n`).Find(buf))
io.WriteString(out, "//\n// -- this file is auto-generated -- do not edit -- edit list.go and run \"go generate\" instead --\n//\n\n")
- for _, t := range []string{"Container", "Specimen"} {
+ for _, t := range []string{"Container", "Specimen", "User"} {
_, err := out.Write(bytes.ReplaceAll(orig, []byte("Collection"), []byte(t)))
if err != nil {
panic(err)
// -- this file is auto-generated -- do not edit -- edit list.go and run "go generate" instead --
//
-func (conn *Conn) ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
+func (conn *Conn) generated_ContainerList(ctx context.Context, options arvados.ListOptions) (arvados.ContainerList, error) {
var mtx sync.Mutex
var merged arvados.ContainerList
var needSort atomic.Value
return merged, err
}
-func (conn *Conn) SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
+func (conn *Conn) generated_SpecimenList(ctx context.Context, options arvados.ListOptions) (arvados.SpecimenList, error) {
var mtx sync.Mutex
var merged arvados.SpecimenList
var needSort atomic.Value
}
return merged, err
}
+
+func (conn *Conn) generated_UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ var mtx sync.Mutex
+ var merged arvados.UserList
+ var needSort atomic.Value
+ needSort.Store(false)
+ err := conn.splitListRequest(ctx, options, func(ctx context.Context, _ string, backend arvados.API, options arvados.ListOptions) ([]string, error) {
+ cl, err := backend.UserList(ctx, options)
+ if err != nil {
+ return nil, err
+ }
+ mtx.Lock()
+ defer mtx.Unlock()
+ if len(merged.Items) == 0 {
+ merged = cl
+ } else if len(cl.Items) > 0 {
+ merged.Items = append(merged.Items, cl.Items...)
+ needSort.Store(true)
+ }
+ uuids := make([]string, 0, len(cl.Items))
+ for _, item := range cl.Items {
+ uuids = append(uuids, item.UUID)
+ }
+ return uuids, nil
+ })
+ if needSort.Load().(bool) {
+ // Apply the default/implied order, "modified_at desc"
+ sort.Slice(merged.Items, func(i, j int) bool {
+ mi, mj := merged.Items[i].ModifiedAt, merged.Items[j].ModifiedAt
+ return mj.Before(mi)
+ })
+ }
+ if merged.Items == nil {
+ // Return empty results as [], not null
+ // (https://github.com/golang/go/issues/27589 might be
+ // a better solution in the future)
+ merged.Items = []arvados.User{}
+ }
+ return merged, err
+}
// CollectionList is used as a template to auto-generate List()
// methods for other types; see generate.go.
-func (conn *Conn) CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
+func (conn *Conn) generated_CollectionList(ctx context.Context, options arvados.ListOptions) (arvados.CollectionList, error) {
var mtx sync.Mutex
var merged arvados.CollectionList
var needSort atomic.Value
"context"
"fmt"
"net/http"
- "net/url"
- "os"
"sort"
- "testing"
- "git.curoverse.com/arvados.git/lib/controller/router"
- "git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "git.curoverse.com/arvados.git/sdk/go/auth"
- "git.curoverse.com/arvados.git/sdk/go/ctxlog"
- "git.curoverse.com/arvados.git/sdk/go/httpserver"
check "gopkg.in/check.v1"
)
-// Gocheck boilerplate
-func Test(t *testing.T) {
- check.TestingT(t)
-}
-
-var (
- _ = check.Suite(&FederationSuite{})
- _ = check.Suite(&CollectionListSuite{})
-)
-
-type FederationSuite struct {
- cluster *arvados.Cluster
- ctx context.Context
- fed *Conn
-}
-
-func (s *FederationSuite) SetUpTest(c *check.C) {
- s.cluster = &arvados.Cluster{
- ClusterID: "aaaaa",
- RemoteClusters: map[string]arvados.RemoteCluster{
- "aaaaa": arvados.RemoteCluster{
- Host: os.Getenv("ARVADOS_API_HOST"),
- },
- },
- }
- arvadostest.SetServiceURL(&s.cluster.Services.RailsAPI, "https://"+os.Getenv("ARVADOS_TEST_API_HOST"))
- s.cluster.TLS.Insecure = true
- s.cluster.API.MaxItemsPerResponse = 3
-
- ctx := context.Background()
- ctx = ctxlog.Context(ctx, ctxlog.TestLogger(c))
- ctx = auth.NewContext(ctx, &auth.Credentials{Tokens: []string{arvadostest.ActiveTokenV2}})
- s.ctx = ctx
-
- s.fed = New(s.cluster)
-}
-
-func (s *FederationSuite) addDirectRemote(c *check.C, id string, backend backend) {
- s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
- Host: "in-process.local",
- }
- s.fed.remotes[id] = backend
-}
-
-func (s *FederationSuite) addHTTPRemote(c *check.C, id string, backend backend) {
- srv := httpserver.Server{Addr: ":"}
- srv.Handler = router.New(backend)
- c.Check(srv.Start(), check.IsNil)
- s.cluster.RemoteClusters[id] = arvados.RemoteCluster{
- Host: srv.Addr,
- Proxy: true,
- }
- s.fed.remotes[id] = rpc.NewConn(id, &url.URL{Scheme: "http", Host: srv.Addr}, true, saltedTokenProvider(s.fed.local, id))
-}
+var _ = check.Suite(&CollectionListSuite{})
type collectionLister struct {
arvadostest.APIStub
check "gopkg.in/check.v1"
)
-func (s *FederationSuite) TestDeferToLoginCluster(c *check.C) {
+var _ = check.Suite(&LoginSuite{})
+
+type LoginSuite struct {
+ FederationSuite
+}
+
+func (s *LoginSuite) TestDeferToLoginCluster(c *check.C) {
s.addHTTPRemote(c, "zhome", &arvadostest.APIStub{})
s.cluster.Login.LoginCluster = "zhome"
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package federation
+
+import (
+ "encoding/json"
+ "errors"
+ "net/url"
+ "os"
+ "strings"
+
+ "git.curoverse.com/arvados.git/lib/controller/rpc"
+ "git.curoverse.com/arvados.git/sdk/go/arvados"
+ "git.curoverse.com/arvados.git/sdk/go/arvadostest"
+ check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&UserSuite{})
+
+type UserSuite struct {
+ FederationSuite
+}
+
+func (s *UserSuite) TestLoginClusterUserList(c *check.C) {
+ s.cluster.ClusterID = "local"
+ s.cluster.Login.LoginCluster = "zzzzz"
+ s.fed = New(s.cluster)
+ s.addDirectRemote(c, "zzzzz", rpc.NewConn("zzzzz", &url.URL{Scheme: "https", Host: os.Getenv("ARVADOS_API_HOST")}, true, rpc.PassthroughTokenProvider))
+
+ for _, updateFail := range []bool{false, true} {
+ for _, opts := range []arvados.ListOptions{
+ {Offset: 0, Limit: -1, Select: nil},
+ {Offset: 1, Limit: 1, Select: nil},
+ {Offset: 0, Limit: 2, Select: []string{"uuid"}},
+ {Offset: 0, Limit: 2, Select: []string{"uuid", "email"}},
+ } {
+ c.Logf("updateFail %v, opts %#v", updateFail, opts)
+ spy := arvadostest.NewProxy(c, s.cluster.Services.RailsAPI)
+ stub := &arvadostest.APIStub{Error: errors.New("local cluster failure")}
+ if updateFail {
+ s.fed.local = stub
+ } else {
+ s.fed.local = rpc.NewConn(s.cluster.ClusterID, spy.URL, true, rpc.PassthroughTokenProvider)
+ }
+ userlist, err := s.fed.UserList(s.ctx, opts)
+ if updateFail && err == nil {
+ // All local updates fail, so the only
+ // cases expected to succeed are the
+ // ones with 0 results.
+ c.Check(userlist.Items, check.HasLen, 0)
+ c.Check(stub.Calls(nil), check.HasLen, 0)
+ } else if updateFail {
+ c.Logf("... err %#v", err)
+ calls := stub.Calls(stub.UserBatchUpdate)
+ if c.Check(calls, check.HasLen, 1) {
+ c.Logf("... stub.UserUpdate called with options: %#v", calls[0].Options)
+ shouldUpdate := map[string]bool{
+ "uuid": false,
+ "email": true,
+ "first_name": true,
+ "last_name": true,
+ "is_admin": true,
+ "is_active": true,
+ "prefs": true,
+ // can't safely update locally
+ "owner_uuid": false,
+ "identity_url": false,
+ // virtual attrs
+ "full_name": false,
+ "is_invited": false,
+ }
+ if opts.Select != nil {
+ // Only the selected
+ // fields (minus uuid)
+ // should be updated.
+ for k := range shouldUpdate {
+ shouldUpdate[k] = false
+ }
+ for _, k := range opts.Select {
+ if k != "uuid" {
+ shouldUpdate[k] = true
+ }
+ }
+ }
+ var uuid string
+ for uuid = range calls[0].Options.(arvados.UserBatchUpdateOptions).Updates {
+ }
+ for k, shouldFind := range shouldUpdate {
+ _, found := calls[0].Options.(arvados.UserBatchUpdateOptions).Updates[uuid][k]
+ c.Check(found, check.Equals, shouldFind, check.Commentf("offending attr: %s", k))
+ }
+ }
+ } else {
+ updates := 0
+ for _, d := range spy.RequestDumps {
+ d := string(d)
+ if strings.Contains(d, "PATCH /arvados/v1/users/batch") {
+ c.Check(d, check.Matches, `(?ms).*Authorization: Bearer `+arvadostest.SystemRootToken+`.*`)
+ updates++
+ }
+ }
+ c.Check(err, check.IsNil)
+ c.Check(updates, check.Equals, 1)
+ c.Logf("... response items %#v", userlist.Items)
+ }
+ }
+ }
+}
+
+// userAttrsCachedFromLoginCluster must have an entry for every field
+// in the User struct.
+func (s *UserSuite) TestUserAttrsUpdateWhitelist(c *check.C) {
+ buf, err := json.Marshal(&arvados.User{})
+ c.Assert(err, check.IsNil)
+ var allFields map[string]interface{}
+ err = json.Unmarshal(buf, &allFields)
+ c.Assert(err, check.IsNil)
+ for k := range allFields {
+ _, ok := userAttrsCachedFromLoginCluster[k]
+ c.Check(ok, check.Equals, true, check.Commentf("field name %q missing from userAttrsCachedFromLoginCluster", k))
+ }
+}
if h.Cluster.EnableBetaController14287 {
mux.Handle("/arvados/v1/collections", rtr)
mux.Handle("/arvados/v1/collections/", rtr)
+ mux.Handle("/arvados/v1/users", rtr)
+ mux.Handle("/arvados/v1/users/", rtr)
mux.Handle("/login", rtr)
}
"strconv"
"strings"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
)
// Parse req as an Arvados V1 API request and return the request
}
}
- routeParams, _ := req.Context().Value(httprouter.ParamsKey).(httprouter.Params)
- for _, p := range routeParams {
- params[p.Key] = p.Value
+ for k, v := range mux.Vars(req) {
+ params[k] = v
}
if v, ok := params[attrsKey]; ok && attrsKey != "" {
"git.curoverse.com/arvados.git/sdk/go/auth"
"git.curoverse.com/arvados.git/sdk/go/ctxlog"
"git.curoverse.com/arvados.git/sdk/go/httpserver"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
"github.com/sirupsen/logrus"
)
type router struct {
- mux *httprouter.Router
+ mux *mux.Router
fed arvados.API
}
func New(fed arvados.API) *router {
rtr := &router{
- mux: httprouter.New(),
+ mux: mux.NewRouter(),
fed: fed,
}
rtr.addRoutes()
return rtr.fed.SpecimenDelete(ctx, *opts.(*arvados.DeleteOptions))
},
},
+ {
+ arvados.EndpointUserCreate,
+ func() interface{} { return &arvados.CreateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserCreate(ctx, *opts.(*arvados.CreateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserMerge,
+ func() interface{} { return &arvados.UserMergeOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserMerge(ctx, *opts.(*arvados.UserMergeOptions))
+ },
+ },
+ {
+ arvados.EndpointUserActivate,
+ func() interface{} { return &arvados.UserActivateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserActivate(ctx, *opts.(*arvados.UserActivateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserSetup,
+ func() interface{} { return &arvados.UserSetupOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserSetup(ctx, *opts.(*arvados.UserSetupOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUnsetup,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUnsetup(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGetCurrent,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGetCurrent(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGetSystem,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGetSystem(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserGet,
+ func() interface{} { return &arvados.GetOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserGet(ctx, *opts.(*arvados.GetOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUpdateUUID,
+ func() interface{} { return &arvados.UpdateUUIDOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUpdateUUID(ctx, *opts.(*arvados.UpdateUUIDOptions))
+ },
+ },
+ {
+ arvados.EndpointUserUpdate,
+ func() interface{} { return &arvados.UpdateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserUpdate(ctx, *opts.(*arvados.UpdateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserList,
+ func() interface{} { return &arvados.ListOptions{Limit: -1} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserList(ctx, *opts.(*arvados.ListOptions))
+ },
+ },
+ {
+ arvados.EndpointUserBatchUpdate,
+ func() interface{} { return &arvados.UserBatchUpdateOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserBatchUpdate(ctx, *opts.(*arvados.UserBatchUpdateOptions))
+ },
+ },
+ {
+ arvados.EndpointUserDelete,
+ func() interface{} { return &arvados.DeleteOptions{} },
+ func(ctx context.Context, opts interface{}) (interface{}, error) {
+ return rtr.fed.UserDelete(ctx, *opts.(*arvados.DeleteOptions))
+ },
+ },
} {
rtr.addRoute(route.endpoint, route.defaultOpts, route.exec)
if route.endpoint.Method == "PATCH" {
rtr.addRoute(endpointPUT, route.defaultOpts, route.exec)
}
}
- rtr.mux.NotFound = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusNotFound)
})
- rtr.mux.MethodNotAllowed = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.MethodNotAllowedHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
httpserver.Errors(w, []string{"API endpoint not found"}, http.StatusMethodNotAllowed)
})
}
func (rtr *router) addRoute(endpoint arvados.APIEndpoint, defaultOpts func() interface{}, exec routableFunc) {
- rtr.mux.HandlerFunc(endpoint.Method, "/"+endpoint.Path, func(w http.ResponseWriter, req *http.Request) {
+ rtr.mux.Methods(endpoint.Method).Path("/" + endpoint.Path).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
logger := ctxlog.FromContext(req.Context())
params, err := rtr.loadRequestParams(req, endpoint.AttrsKey)
if err != nil {
}
creds := auth.CredentialsFromRequest(req)
+ err = creds.LoadTokensFromHTTPRequestBody(req)
+ if err != nil {
+ rtr.sendError(w, fmt.Errorf("error loading tokens from request body: %s", err))
+ return
+ }
if rt, _ := params["reader_tokens"].([]interface{}); len(rt) > 0 {
for _, t := range rt {
if t, ok := t.(string); ok {
"git.curoverse.com/arvados.git/lib/controller/rpc"
"git.curoverse.com/arvados.git/sdk/go/arvados"
"git.curoverse.com/arvados.git/sdk/go/arvadostest"
- "github.com/julienschmidt/httprouter"
+ "github.com/gorilla/mux"
check "gopkg.in/check.v1"
)
func (s *RouterSuite) SetUpTest(c *check.C) {
s.stub = arvadostest.APIStub{}
s.rtr = &router{
- mux: httprouter.New(),
+ mux: mux.NewRouter(),
fed: &s.stub,
}
s.rtr.addRoutes()
params["reader_tokens"] = tokens[1:]
}
path := ep.Path
- if strings.Contains(ep.Path, "/:uuid") {
+ if strings.Contains(ep.Path, "/{uuid}") {
uuid, _ := params["uuid"].(string)
- path = strings.Replace(path, "/:uuid", "/"+uuid, 1)
+ path = strings.Replace(path, "/{uuid}", "/"+uuid, 1)
delete(params, "uuid")
}
return aClient.RequestAndDecodeContext(ctx, dst, ep.Method, path, body, params)
return resp, err
}
+func (conn *Conn) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserCreate
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdate
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp map[string]interface{}
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserUpdateUUID
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGet
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGetCurrent
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserGetSystem
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ ep := arvados.EndpointUserList
+ var resp arvados.UserList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+func (conn *Conn) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ ep := arvados.EndpointUserDelete
+ var resp arvados.User
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
+
func (conn *Conn) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
ep := arvados.EndpointAPIClientAuthorizationCurrent
var resp arvados.APIClientAuthorization
err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
return resp, err
}
+
+func (conn *Conn) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ ep := arvados.APIEndpoint{Method: "PATCH", Path: "arvados/v1/users/batch_update"}
+ var resp arvados.UserList
+ err := conn.requestAndDecode(ctx, &resp, ep, nil, options)
+ return resp, err
+}
EndpointConfigGet = APIEndpoint{"GET", "arvados/v1/config", ""}
EndpointLogin = APIEndpoint{"GET", "login", ""}
EndpointCollectionCreate = APIEndpoint{"POST", "arvados/v1/collections", "collection"}
- EndpointCollectionUpdate = APIEndpoint{"PATCH", "arvados/v1/collections/:uuid", "collection"}
- EndpointCollectionGet = APIEndpoint{"GET", "arvados/v1/collections/:uuid", ""}
+ EndpointCollectionUpdate = APIEndpoint{"PATCH", "arvados/v1/collections/{uuid}", "collection"}
+ EndpointCollectionGet = APIEndpoint{"GET", "arvados/v1/collections/{uuid}", ""}
EndpointCollectionList = APIEndpoint{"GET", "arvados/v1/collections", ""}
- EndpointCollectionProvenance = APIEndpoint{"GET", "arvados/v1/collections/:uuid/provenance", ""}
- EndpointCollectionUsedBy = APIEndpoint{"GET", "arvados/v1/collections/:uuid/used_by", ""}
- EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/:uuid", ""}
- EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/:uuid/trash", ""}
- EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/:uuid/untrash", ""}
+ EndpointCollectionProvenance = APIEndpoint{"GET", "arvados/v1/collections/{uuid}/provenance", ""}
+ EndpointCollectionUsedBy = APIEndpoint{"GET", "arvados/v1/collections/{uuid}/used_by", ""}
+ EndpointCollectionDelete = APIEndpoint{"DELETE", "arvados/v1/collections/{uuid}", ""}
+ EndpointCollectionTrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/trash", ""}
+ EndpointCollectionUntrash = APIEndpoint{"POST", "arvados/v1/collections/{uuid}/untrash", ""}
EndpointSpecimenCreate = APIEndpoint{"POST", "arvados/v1/specimens", "specimen"}
- EndpointSpecimenUpdate = APIEndpoint{"PATCH", "arvados/v1/specimens/:uuid", "specimen"}
- EndpointSpecimenGet = APIEndpoint{"GET", "arvados/v1/specimens/:uuid", ""}
+ EndpointSpecimenUpdate = APIEndpoint{"PATCH", "arvados/v1/specimens/{uuid}", "specimen"}
+ EndpointSpecimenGet = APIEndpoint{"GET", "arvados/v1/specimens/{uuid}", ""}
EndpointSpecimenList = APIEndpoint{"GET", "arvados/v1/specimens", ""}
- EndpointSpecimenDelete = APIEndpoint{"DELETE", "arvados/v1/specimens/:uuid", ""}
+ EndpointSpecimenDelete = APIEndpoint{"DELETE", "arvados/v1/specimens/{uuid}", ""}
EndpointContainerCreate = APIEndpoint{"POST", "arvados/v1/containers", "container"}
- EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/:uuid", "container"}
- EndpointContainerGet = APIEndpoint{"GET", "arvados/v1/containers/:uuid", ""}
+ EndpointContainerUpdate = APIEndpoint{"PATCH", "arvados/v1/containers/{uuid}", "container"}
+ EndpointContainerGet = APIEndpoint{"GET", "arvados/v1/containers/{uuid}", ""}
EndpointContainerList = APIEndpoint{"GET", "arvados/v1/containers", ""}
- EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/:uuid", ""}
- EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/:uuid/lock", ""}
- EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/:uuid/unlock", ""}
+ EndpointContainerDelete = APIEndpoint{"DELETE", "arvados/v1/containers/{uuid}", ""}
+ EndpointContainerLock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/lock", ""}
+ EndpointContainerUnlock = APIEndpoint{"POST", "arvados/v1/containers/{uuid}/unlock", ""}
+ EndpointUserActivate = APIEndpoint{"POST", "arvados/v1/users/{uuid}/activate", ""}
+ EndpointUserCreate = APIEndpoint{"POST", "arvados/v1/users", "user"}
+ EndpointUserCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""}
+ EndpointUserDelete = APIEndpoint{"DELETE", "arvados/v1/users/{uuid}", ""}
+ EndpointUserGet = APIEndpoint{"GET", "arvados/v1/users/{uuid}", ""}
+ EndpointUserGetCurrent = APIEndpoint{"GET", "arvados/v1/users/current", ""}
+ EndpointUserGetSystem = APIEndpoint{"GET", "arvados/v1/users/system", ""}
+ EndpointUserList = APIEndpoint{"GET", "arvados/v1/users", ""}
+ EndpointUserMerge = APIEndpoint{"POST", "arvados/v1/users/merge", ""}
+ EndpointUserSetup = APIEndpoint{"POST", "arvados/v1/users/setup", ""}
+ EndpointUserSystem = APIEndpoint{"GET", "arvados/v1/users/system", ""}
+ EndpointUserUnsetup = APIEndpoint{"POST", "arvados/v1/users/{uuid}/unsetup", ""}
+ EndpointUserUpdate = APIEndpoint{"PATCH", "arvados/v1/users/{uuid}", "user"}
+ EndpointUserUpdateUUID = APIEndpoint{"POST", "arvados/v1/users/{uuid}/update_uuid", ""}
+ EndpointUserBatchUpdate = APIEndpoint{"PATCH", "arvados/v1/users/batch", ""}
EndpointAPIClientAuthorizationCurrent = APIEndpoint{"GET", "arvados/v1/api_client_authorizations/current", ""}
)
Attrs map[string]interface{} `json:"attrs"`
}
+type UpdateUUIDOptions struct {
+ UUID string `json:"uuid"`
+ NewUUID string `json:"new_uuid"`
+}
+
+type UserActivateOptions struct {
+ UUID string `json:"uuid"`
+}
+
+type UserSetupOptions struct {
+ UUID string `json:"uuid"`
+ Email string `json:"email"`
+ OpenIDPrefix string `json:"openid_prefix"`
+ RepoName string `json:"repo_name"`
+ VMUUID string `json:"vm_uuid"`
+ SendNotificationEmail bool `json:"send_notification_email"`
+ Attrs map[string]interface{} `json:"attrs"`
+}
+
+type UserMergeOptions struct {
+ NewUserUUID string `json:"new_user_uuid,omitempty"`
+ OldUserUUID string `json:"old_user_uuid,omitempty"`
+ NewUserToken string `json:"new_user_token,omitempty"`
+}
+
+type UserBatchUpdateOptions struct {
+ Updates map[string]map[string]interface{} `json:"updates"`
+}
+
+type UserBatchUpdateResponse struct{}
+
type DeleteOptions struct {
UUID string `json:"uuid"`
}
SpecimenGet(ctx context.Context, options GetOptions) (Specimen, error)
SpecimenList(ctx context.Context, options ListOptions) (SpecimenList, error)
SpecimenDelete(ctx context.Context, options DeleteOptions) (Specimen, error)
+ UserCreate(ctx context.Context, options CreateOptions) (User, error)
+ UserUpdate(ctx context.Context, options UpdateOptions) (User, error)
+ UserUpdateUUID(ctx context.Context, options UpdateUUIDOptions) (User, error)
+ UserMerge(ctx context.Context, options UserMergeOptions) (User, error)
+ UserActivate(ctx context.Context, options UserActivateOptions) (User, error)
+ UserSetup(ctx context.Context, options UserSetupOptions) (map[string]interface{}, error)
+ UserUnsetup(ctx context.Context, options GetOptions) (User, error)
+ UserGet(ctx context.Context, options GetOptions) (User, error)
+ UserGetCurrent(ctx context.Context, options GetOptions) (User, error)
+ UserGetSystem(ctx context.Context, options GetOptions) (User, error)
+ UserList(ctx context.Context, options ListOptions) (UserList, error)
+ UserDelete(ctx context.Context, options DeleteOptions) (User, error)
+ UserBatchUpdate(context.Context, UserBatchUpdateOptions) (UserList, error)
APIClientAuthorizationCurrent(ctx context.Context, options GetOptions) (APIClientAuthorization, error)
}
package arvados
+import "time"
+
// User is an arvados#user record
type User struct {
- UUID string `json:"uuid"`
- IsActive bool `json:"is_active"`
- IsAdmin bool `json:"is_admin"`
- Username string `json:"username"`
- Email string `json:"email"`
+ UUID string `json:"uuid"`
+ IsActive bool `json:"is_active"`
+ IsAdmin bool `json:"is_admin"`
+ Username string `json:"username"`
+ Email string `json:"email"`
+ FullName string `json:"full_name"`
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ IdentityURL string `json:"identity_url"`
+ IsInvited bool `json:"is_invited"`
+ OwnerUUID string `json:"owner_uuid"`
+ CreatedAt time.Time `json:"created_at"`
+ ModifiedAt time.Time `json:"modified_at"`
+ ModifiedByUserUUID string `json:"modified_by_user_uuid"`
+ ModifiedByClientUUID string `json:"modified_by_client_uuid"`
+ Prefs map[string]interface{} `json:"prefs"`
}
// UserList is an arvados#userList resource.
as.appendCall(as.SpecimenDelete, ctx, options)
return arvados.Specimen{}, as.Error
}
+func (as *APIStub) UserCreate(ctx context.Context, options arvados.CreateOptions) (arvados.User, error) {
+ as.appendCall(as.UserCreate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserUpdate(ctx context.Context, options arvados.UpdateOptions) (arvados.User, error) {
+ as.appendCall(as.UserUpdate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserUpdateUUID(ctx context.Context, options arvados.UpdateUUIDOptions) (arvados.User, error) {
+ as.appendCall(as.UserUpdateUUID, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserActivate(ctx context.Context, options arvados.UserActivateOptions) (arvados.User, error) {
+ as.appendCall(as.UserActivate, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserSetup(ctx context.Context, options arvados.UserSetupOptions) (map[string]interface{}, error) {
+ as.appendCall(as.UserSetup, ctx, options)
+ return nil, as.Error
+}
+func (as *APIStub) UserUnsetup(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserUnsetup, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGet(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGet, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGetCurrent(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGetCurrent, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserGetSystem(ctx context.Context, options arvados.GetOptions) (arvados.User, error) {
+ as.appendCall(as.UserGetSystem, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserList(ctx context.Context, options arvados.ListOptions) (arvados.UserList, error) {
+ as.appendCall(as.UserList, ctx, options)
+ return arvados.UserList{}, as.Error
+}
+func (as *APIStub) UserDelete(ctx context.Context, options arvados.DeleteOptions) (arvados.User, error) {
+ as.appendCall(as.UserDelete, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserMerge(ctx context.Context, options arvados.UserMergeOptions) (arvados.User, error) {
+ as.appendCall(as.UserMerge, ctx, options)
+ return arvados.User{}, as.Error
+}
+func (as *APIStub) UserBatchUpdate(ctx context.Context, options arvados.UserBatchUpdateOptions) (arvados.UserList, error) {
+ as.appendCall(as.UserBatchUpdate, ctx, options)
+ return arvados.UserList{}, as.Error
+}
func (as *APIStub) APIClientAuthorizationCurrent(ctx context.Context, options arvados.GetOptions) (arvados.APIClientAuthorization, error) {
as.appendCall(as.APIClientAuthorizationCurrent, ctx, options)
return arvados.APIClientAuthorization{}, as.Error
defer as.mtx.Unlock()
var calls []APIStubCall
for _, call := range as.calls {
-
if method == nil || (runtime.FuncForPC(reflect.ValueOf(call.Method).Pointer()).Name() ==
runtime.FuncForPC(reflect.ValueOf(method).Pointer()).Name()) {
calls = append(calls, call)
AdminToken = "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h"
AnonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
DataManagerToken = "320mkve8qkswstz7ff61glpk3mhgghmg67wmic7elw4z41pke1"
+ SystemRootToken = "systemusertesttoken1234567890aoeuidhtnsqjkxbmwvzpy"
ManagementToken = "jg3ajndnq63sywcd50gbs5dskdc9ckkysb0nsqmfz08nwf17nl"
ActiveUserUUID = "zzzzz-tpzed-xurymjxw79nv3jz"
FederatedActiveUserUUID = "zbbbb-tpzed-xurymjxw79nv3jz"
"zzzzz": {
"EnableBetaController14287": ('14287' in os.environ.get('ARVADOS_EXPERIMENTAL', '')),
"ManagementToken": "e687950a23c3a9bceec28c6223a06c79",
- "SystemRootToken": auth_token('data_manager'),
+ "SystemRootToken": auth_token('system_user'),
"API": {
"RequestTimeout": "30s",
},
uglifier (~> 2.0)
BUNDLED WITH
- 2.0.2
+ 1.17.3
class Arvados::V1::UsersController < ApplicationController
accept_attribute_as_json :prefs, Hash
+ accept_param_as_json :updates
skip_before_action :find_object_by_uuid, only:
- [:activate, :current, :system, :setup, :merge]
+ [:activate, :current, :system, :setup, :merge, :batch_update]
skip_before_action :render_404_if_no_object, only:
- [:activate, :current, :system, :setup, :merge]
- before_action :admin_required, only: [:setup, :unsetup, :update_uuid]
+ [:activate, :current, :system, :setup, :merge, :batch_update]
+ before_action :admin_required, only: [:setup, :unsetup, :update_uuid, :batch_update]
+
+ # Internal API used by controller to update local cache of user
+ # records from LoginCluster.
+ def batch_update
+ @objects = []
+ params[:updates].andand.each do |uuid, attrs|
+ begin
+ u = User.find_or_create_by(uuid: uuid)
+ rescue ActiveRecord::RecordNotUnique
+ retry
+ end
+ u.update_attributes!(attrs)
+ @objects << u
+ end
+ @offset = 0
+ @limit = -1
+ render_list
+ end
def current
if current_user
clnt
end
+ def self.check_system_root_token token
+ if token == Rails.configuration.SystemRootToken
+ return ApiClientAuthorization.new(user: User.find_by_uuid(system_user_uuid),
+ api_token: token,
+ api_client: ApiClient.new(is_trusted: true, url_prefix: ""))
+ else
+ return nil
+ end
+ end
+
def self.validate(token:, remote: nil)
- return nil if !token
+ return nil if token.nil? or token.empty?
remote ||= Rails.configuration.ClusterID
+ auth = self.check_system_root_token(token)
+ if !auth.nil?
+ return auth
+ end
+
case token[0..2]
when 'v2/'
_, token_uuid, secret, optional = token.split('/')
# Sync user record.
if remote_user_prefix == Rails.configuration.Login.LoginCluster
- # Remote cluster controls our user database, copy both
- # 'is_active' and 'is_admin'
- user.is_active = remote_user['is_active']
+ # Remote cluster controls our user database, set is_active if
+ # remote is active. If remote is not active, user will be
+ # unsetup (see below).
+ user.is_active = true if remote_user['is_active']
user.is_admin = remote_user['is_admin']
else
if Rails.configuration.Users.NewUsersAreActive ||
Rails.configuration.RemoteClusters[remote_user_prefix].andand["ActivateUsers"]
- # Default policy is to activate users, so match activate
- # with the remote record.
- user.is_active = remote_user['is_active']
- elsif !remote_user['is_active']
- # Deactivate user if the remote is inactive, otherwise don't
- # change 'is_active'.
- user.is_active = false
+ # Default policy is to activate users
+ user.is_active = true if remote_user['is_active']
end
end
end
act_as_system_user do
+ if user.is_active && !remote_user['is_active']
+ user.unsetup
+ end
+
user.save!
# We will accept this token (and avoid reloading the user
},
uniqueness: true,
allow_nil: true)
+ validate :must_unsetup_to_deactivate
before_update :prevent_privilege_escalation
before_update :prevent_inactive_admin
before_update :verify_repositories_empty, :if => Proc.new { |user|
# create links
def setup(openid_prefix:, repo_name: nil, vm_uuid: nil)
- oid_login_perm = create_oid_login_perm openid_prefix
repo_perm = create_user_repo_link repo_name
vm_login_perm = create_vm_login_permission_link(vm_uuid, username) if vm_uuid
group_perm = create_user_group_link
- return [oid_login_perm, repo_perm, vm_login_perm, group_perm, self].compact
+ return [repo_perm, vm_login_perm, group_perm, self].compact
end
# delete user signatures, login, repo, and vm perms, and mark as inactive
def unsetup
# delete oid_login_perms for this user
+ #
+ # note: these permission links are obsolete, they have no effect
+ # on anything and they are not created for new users.
Link.where(tail_uuid: self.email,
link_class: 'permission',
name: 'can_login').destroy_all
self.save!
end
+ def must_unsetup_to_deactivate
+ if self.is_active_changed? &&
+ self.is_active_was == true &&
+ !self.is_active
+
+ group = Group.where(name: 'All users').select do |g|
+ g[:uuid].match(/-f+$/)
+ end.first
+
+ # When a user is set up, they are added to the "All users"
+ # group. A user that is part of the "All users" group is
+ # allowed to self-activate.
+ #
+ # It doesn't make sense to deactivate a user (set is_active =
+ # false) without first removing them from the "All users" group,
+ # because they would be able to immediately reactivate
+ # themselves.
+ #
+ # The 'unsetup' method removes the user from the "All users"
+ # group (and also sets is_active = false) so send a message
+ # explaining the correct way to deactivate a user.
+ #
+ if Link.where(tail_uuid: self.uuid,
+ head_uuid: group[:uuid],
+ link_class: 'permission',
+ name: 'can_read').any?
+ errors.add :is_active, "cannot be set to false directly, use the 'Deactivate' button on Workbench, or the 'unsetup' API call"
+ end
+ end
+ end
+
def set_initial_username(requested: false)
if !requested.is_a?(String) || requested.empty?
email_parts = email.partition("@")
merged
end
- def create_oid_login_perm(openid_prefix)
- # Check oid_login_perm
- oid_login_perms = Link.where(tail_uuid: self.email,
- head_uuid: self.uuid,
- link_class: 'permission',
- name: 'can_login')
-
- if !oid_login_perms.any?
- # create openid login permission
- oid_login_perm = Link.create!(link_class: 'permission',
- name: 'can_login',
- tail_uuid: self.email,
- head_uuid: self.uuid,
- properties: {
- "identity_url_prefix" => openid_prefix,
- })
- logger.info { "openid login permission: " + oid_login_perm[:uuid] }
- else
- oid_login_perm = oid_login_perms.first
- end
-
- return oid_login_perm
- end
-
def create_user_repo_link(repo_name)
# repo_name is optional
if not repo_name
arvcfg.declare_config "Login.LoginCluster", String
arvcfg.declare_config "Login.RemoteTokenRefresh", ActiveSupport::Duration
arvcfg.declare_config "TLS.Insecure", Boolean, :sso_insecure
-arvcfg.declare_config "Services.SSO.ExternalURL", NonemptyString, :sso_provider_url
+arvcfg.declare_config "Services.SSO.ExternalURL", String, :sso_provider_url
arvcfg.declare_config "AuditLogs.MaxAge", ActiveSupport::Duration, :max_audit_log_age
arvcfg.declare_config "AuditLogs.MaxDeleteBatch", Integer, :max_audit_log_delete_batch
arvcfg.declare_config "AuditLogs.UnloggedAttributes", Hash, :unlogged_attributes, ->(cfg, k, v) { arrayToHash cfg, "AuditLogs.UnloggedAttributes", v }
post 'unsetup', on: :member
post 'update_uuid', on: :member
post 'merge', on: :collection
+ patch 'batch_update', on: :collection
end
resources :virtual_machines do
get 'logins', on: :member
end
act_as_system_user do
u = users(:active)
- u.is_active = false
+ u.unsetup
u.save!
end
authorize_with :admin
assert_not_nil created['email'], 'expected non-nil email'
assert_nil created['identity_url'], 'expected no identity_url'
- # arvados#user, repo link and link add user to 'All users' group
- verify_links_added 4
-
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'User'
+ # repo link and link add user to 'All users' group
+ verify_links_added 3
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
"foo/#{repo_name}", created['uuid'], 'arvados#repository', true, 'Repository'
assert_not_nil response_object['uuid'], 'expected uuid for the new user'
assert_equal response_object['email'], 'foo@example.com', 'expected given email'
- # four extra links; system_group, login, group and repo perms
- verify_links_added 4
+ # three extra links; system_group, group and repo perms
+ verify_links_added 3
end
test "setup user with fake vm and expect error" do
assert_not_nil response_object['uuid'], 'expected uuid for the new user'
assert_equal response_object['email'], 'foo@example.com', 'expected given email'
- # five extra links; system_group, login, group, vm, repo
- verify_links_added 5
+ # four extra links; system_group, group, vm, repo
+ verify_links_added 4
end
test "setup user with valid email, no vm and no repo as input" do
assert_not_nil response_object['uuid'], 'expected uuid for new user'
assert_equal response_object['email'], 'foo@example.com', 'expected given email'
- # three extra links; system_group, login, and group
- verify_links_added 3
-
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- response_object['uuid'], response_object['email'], 'arvados#user', false, 'User'
+ # two extra links; system_group, and group
+ verify_links_added 2
verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
'All users', response_object['uuid'], 'arvados#group', true, 'Group'
assert_equal 'test_first_name', response_object['first_name'],
'expecting first name'
- # five extra links; system_group, login, group, repo and vm
- verify_links_added 5
+ # four extra links; system_group, group, repo and vm
+ verify_links_added 4
end
test "setup user with an existing user email and check different object is created" do
assert_not_equal response_object['uuid'], inactive_user['uuid'],
'expected different uuid after create operation'
assert_equal inactive_user['email'], response_object['email'], 'expected given email'
- # system_group, openid, group, and repo. No vm link.
- verify_links_added 4
+ # system_group, group, and repo. No vm link.
+ verify_links_added 3
end
test "setup user with openid prefix" do
assert_nil created['identity_url'], 'expected no identity_url'
# verify links
- # four new links: system_group, arvados#user, repo, and 'All users' group.
- verify_links_added 4
-
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'User'
+ # three new links: system_group, repo, and 'All users' group.
+ verify_links_added 3
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
assert_not_nil created['email'], 'expected non-nil email'
assert_nil created['identity_url'], 'expected no identity_url'
- # five new links: system_group, arvados#user, repo, vm and 'All
- # users' group link
- verify_links_added 5
+ # four new links: system_group, repo, vm and 'All users' group link
+ verify_links_added 4
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'User'
+ # system_group isn't part of the response. See User#add_system_group_permission_link
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
assert_nil(users(:project_viewer).redirect_to_user_uuid)
end
+ test "batch update fails for non-admin" do
+ authorize_with(:active)
+ patch(:batch_update, params: {updates: {}})
+ assert_response(403)
+ end
+
+ test "batch update" do
+ existinguuid = 'remot-tpzed-foobarbazwazqux'
+ newuuid = 'remot-tpzed-newnarnazwazqux'
+ act_as_system_user do
+ User.create!(uuid: existinguuid, email: 'root@existing.example.com')
+ end
+
+ authorize_with(:admin)
+ patch(:batch_update,
+ params: {
+ updates: {
+ existinguuid => {
+ 'first_name' => 'root',
+ 'email' => 'root@remot.example.com',
+ 'is_active' => true,
+ 'is_admin' => true,
+ 'prefs' => {'foo' => 'bar'},
+ },
+ newuuid => {
+ 'first_name' => 'noot',
+ 'email' => 'root@remot.example.com',
+ },
+ }})
+ assert_response(:success)
+
+ assert_equal('root', User.find_by_uuid(existinguuid).first_name)
+ assert_equal('root@remot.example.com', User.find_by_uuid(existinguuid).email)
+ assert_equal(true, User.find_by_uuid(existinguuid).is_active)
+ assert_equal(true, User.find_by_uuid(existinguuid).is_admin)
+ assert_equal({'foo' => 'bar'}, User.find_by_uuid(existinguuid).prefs)
+
+ assert_equal('noot', User.find_by_uuid(newuuid).first_name)
+ assert_equal('root@remot.example.com', User.find_by_uuid(newuuid).email)
+ end
+
NON_ADMIN_USER_DATA = ["uuid", "kind", "is_active", "email", "first_name",
"last_name", "username"].sort
oid_login_perms = Link.where(tail_uuid: email,
link_class: 'permission',
name: 'can_login').where("head_uuid like ?", User.uuid_like_pattern)
- if expect_oid_login_perms
- assert oid_login_perms.any?, "expected oid_login_perms"
- else
- assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
- end
+
+ # these don't get added any more! they shouldn't appear ever.
+ assert !oid_login_perms.any?, "expected all oid_login_perms deleted"
repo_perms = Link.where(tail_uuid: uuid,
link_class: 'permission',
assert_equal 'blarney@example.com', json_response['email']
end
+ test 'remote user is deactivated' do
+ Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = true
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response :success
+ assert_equal true, json_response['is_active']
+
+ # revoke original token
+ @stub_content[:is_active] = false
+
+ # simulate cache expiry
+ ApiClientAuthorization.where(
+ uuid: salted_active_token(remote: 'zbbbb').split('/')[1]).
+ update_all(expires_at: db_current_time - 1.minute)
+
+ # re-authorize after cache expires
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_equal false, json_response['is_active']
+
+ end
+
test 'authenticate with remote token, remote username conflicts with local' do
@stub_content[:username] = 'active'
get '/arvados/v1/users/current',
refute_includes(group_uuids, groups(:testusergroup_admins).uuid)
end
+ test 'do not auto-activate user from untrusted cluster' do
+ Rails.configuration.RemoteClusters['zbbbb'].AutoSetupNewUsers = false
+ Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = false
+ get '/arvados/v1/users/current',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response :success
+ assert_equal 'zbbbb-tpzed-000000000000000', json_response['uuid']
+ assert_equal false, json_response['is_admin']
+ assert_equal false, json_response['is_active']
+ assert_equal 'foo@example.com', json_response['email']
+ assert_equal 'barney', json_response['username']
+ post '/arvados/v1/users/zbbbb-tpzed-000000000000000/activate',
+ params: {format: 'json'},
+ headers: auth(remote: 'zbbbb')
+ assert_response 422
+ end
+
test 'auto-activate user from trusted cluster' do
Rails.configuration.RemoteClusters['zbbbb'].ActivateUsers = true
get '/arvados/v1/users/current',
].each do |testcase|
test "user auto-activate #{testcase.inspect}" do
# Configure auto_setup behavior according to testcase[:cfg]
+ Rails.configuration.Users.NewUsersAreActive = false
Rails.configuration.Users.AutoSetupNewUsers = testcase[:cfg][:auto]
Rails.configuration.Users.AutoSetupNewUsersWithVmUUID =
(testcase[:cfg][:vm] ? virtual_machines(:testvm).uuid : "")
assert_not_nil created['email'], 'expected non-nil email'
assert_nil created['identity_url'], 'expected no identity_url'
- # arvados#user, repo link and link add user to 'All users' group
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'arvados#user'
+ # repo link and link add user to 'All users' group
verify_link response_items, 'arvados#repository', true, 'permission', 'can_manage',
'foo/usertestrepo', created['uuid'], 'arvados#repository', true, 'Repository'
assert_not_nil created['email'], 'expected non-nil email'
assert_equal created['email'], 'foo@example.com', 'expected input email'
- # three new links: system_group, arvados#user, and 'All users' group.
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'arvados#user'
+ # two new links: system_group, and 'All users' group.
verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
'All users', created['uuid'], 'arvados#group', true, 'Group'
assert_not_nil created['uuid'], 'expected uuid for the new user'
assert_equal created['email'], 'foo@example.com', 'expected given email'
- # five extra links: system_group, login, group, repo and vm
- verify_link response_items, 'arvados#user', true, 'permission', 'can_login',
- created['uuid'], created['email'], 'arvados#user', false, 'arvados#user'
+ # four extra links: system_group, login, group, repo and vm
verify_link response_items, 'arvados#group', true, 'permission', 'can_read',
'All users', created['uuid'], 'arvados#group', true, 'Group'
end
+ test "cannot set is_activate to false directly" do
+ post('/arvados/v1/users',
+ params: {
+ user: {
+ email: "bob@example.com",
+ username: "bobby"
+ },
+ },
+ headers: auth(:admin))
+ assert_response(:success)
+ user = json_response
+ assert_equal false, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: auth(:admin))
+ assert_response(:success)
+ user = json_response
+ assert_equal true, user['is_active']
+
+ put("/arvados/v1/users/#{user['uuid']}",
+ params: {
+ user: {is_active: false}
+ },
+ headers: auth(:admin))
+ assert_response 422
+ end
+
+ test "cannot self activate when AutoSetupNewUsers is false" do
+ Rails.configuration.Users.NewUsersAreActive = false
+ Rails.configuration.Users.AutoSetupNewUsers = false
+
+ user = nil
+ token = nil
+ act_as_system_user do
+ user = User.create!(email: "bob@example.com", username: "bobby")
+ ap = ApiClientAuthorization.create!(user: user, api_client: ApiClient.all.first)
+ token = ap.api_token
+ end
+
+ get("/arvados/v1/users/#{user['uuid']}",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response(:success)
+ user = json_response
+ assert_equal false, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response 422
+ assert_match(/Cannot activate without being invited/, json_response['errors'][0])
+ end
+
+
+ test "cannot self activate after unsetup" do
+ Rails.configuration.Users.NewUsersAreActive = false
+ Rails.configuration.Users.AutoSetupNewUsers = false
+
+ user = nil
+ token = nil
+ act_as_system_user do
+ user = User.create!(email: "bob@example.com", username: "bobby")
+ ap = ApiClientAuthorization.create!(user: user, api_client_id: 0)
+ token = ap.api_token
+ end
+
+ post("/arvados/v1/users/setup",
+ params: {uuid: user['uuid']},
+ headers: auth(:admin))
+ assert_response :success
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response 403
+ assert_match(/Cannot activate without user agreements/, json_response['errors'][0])
+
+ post("/arvados/v1/user_agreements/sign",
+ params: {uuid: 'zzzzz-4zz18-t68oksiu9m80s4y'},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response :success
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response :success
+
+ get("/arvados/v1/users/#{user['uuid']}",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response(:success)
+ user = json_response
+ assert_equal true, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/unsetup",
+ params: {},
+ headers: auth(:admin))
+ assert_response :success
+
+ get("/arvados/v1/users/#{user['uuid']}",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response(:success)
+ user = json_response
+ assert_equal false, user['is_active']
+
+ post("/arvados/v1/users/#{user['uuid']}/activate",
+ params: {},
+ headers: {"HTTP_AUTHORIZATION" => "Bearer #{token}"})
+ assert_response 422
+ assert_match(/Cannot activate without being invited/, json_response['errors'][0])
+ end
+
+
end
assert_empty ApiClientAuthorization.where(uuid: api_client_authorizations(:expired).uuid)
end
+ test "accepts SystemRootToken" do
+ assert_nil ApiClientAuthorization.validate(token: "xxxSystemRootTokenxxx")
+
+ # will create a new ApiClientAuthorization record
+ Rails.configuration.SystemRootToken = "xxxSystemRootTokenxxx"
+
+ auth = ApiClientAuthorization.validate(token: "xxxSystemRootTokenxxx")
+ assert_equal "xxxSystemRootTokenxxx", auth.api_token
+ assert_equal User.find_by_uuid(system_user_uuid).id, auth.user_id
+ assert auth.api_client.is_trusted
+
+ # now change the token and try to use the old one first
+ Rails.configuration.SystemRootToken = "newxxxSystemRootTokenxxx"
+
+ # old token will fail
+ assert_nil ApiClientAuthorization.validate(token: "xxxSystemRootTokenxxx")
+ # new token will work
+ auth = ApiClientAuthorization.validate(token: "newxxxSystemRootTokenxxx")
+ assert_equal "newxxxSystemRootTokenxxx", auth.api_token
+ assert_equal User.find_by_uuid(system_user_uuid).id, auth.user_id
+
+ # now change the token again and use the new one first
+ Rails.configuration.SystemRootToken = "new2xxxSystemRootTokenxxx"
+
+ # new token will work
+ auth = ApiClientAuthorization.validate(token: "new2xxxSystemRootTokenxxx")
+ assert_equal "new2xxxSystemRootTokenxxx", auth.api_token
+ assert_equal User.find_by_uuid(system_user_uuid).id, auth.user_id
+ # old token will fail
+ assert_nil ApiClientAuthorization.validate(token: "newxxxSystemRootTokenxxx")
+ end
+
+
end
resp_user = find_obj_in_resp response, 'User'
verify_user resp_user, email
- oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
-
- verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
- resp_user[:uuid]
-
- assert_equal openid_prefix, oid_login_perm[:properties]['identity_url_prefix'],
- 'expected identity_url_prefix not found for oid_login_perm'
-
group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
resp_user = find_obj_in_resp response, 'User'
verify_user resp_user, email
- oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
-
- verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
- resp_user[:uuid]
-
- assert_equal openid_prefix, oid_login_perm[:properties]['identity_url_prefix'],
- 'expected identity_url_prefix not found for oid_login_perm'
-
group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
resp_user = find_obj_in_resp response, 'User'
verify_user resp_user, email
- oid_login_perm = find_obj_in_resp response, 'Link', 'arvados#user'
- verify_link oid_login_perm, 'permission', 'can_login', resp_user[:email],
- resp_user[:uuid]
- assert_equal openid_prefix, oid_login_perm[:properties]['identity_url_prefix'],
- 'expected identity_url_prefix not found for oid_login_perm'
-
group_perm = find_obj_in_resp response, 'Link', 'arvados#group'
verify_link group_perm, 'permission', 'can_read', resp_user[:uuid], nil
verify_link_exists(Rails.configuration.Users.AutoSetupNewUsers || active,
groups(:all_users).uuid, user.uuid,
"permission", "can_read")
- # Check for OID login link.
- verify_link_exists(Rails.configuration.Users.AutoSetupNewUsers || active,
- user.uuid, user.email, "permission", "can_login")
+
# Check for repository.
if named_repo = (prior_repo or
Repository.where(name: expect_repo_name).first)
arvadostest.StartKeep(4, true)
arv, err := arvadosclient.MakeArvadosClient()
- arv.ApiToken = arvadostest.DataManagerToken
+ arv.ApiToken = arvadostest.SystemRootToken
c.Assert(err, check.IsNil)
s.keepClient, err = keepclient.MakeKeepClient(arv)
s.client = &arvados.Client{
APIHost: os.Getenv("ARVADOS_API_HOST"),
- AuthToken: arvadostest.DataManagerToken,
+ AuthToken: arvadostest.SystemRootToken,
Insecure: true,
}
}
_, _, err = kc.PutB([]byte("some-more-index-data"))
c.Check(err, IsNil)
- kc.Arvados.ApiToken = arvadostest.DataManagerToken
+ kc.Arvados.ApiToken = arvadostest.SystemRootToken
// Invoke GetIndex
for _, spec := range []struct {
if err != nil {
t.Fatal(err)
}
- cluster.SystemRootToken = arvadostest.DataManagerToken
+ cluster.SystemRootToken = arvadostest.SystemRootToken
cluster.ManagementToken = arvadostest.ManagementToken
cluster.Collections.BlobSigning = false
return cluster
c.Check(resp.Body.String(), check.Equals, "Unauthorized\n")
}
- tok := arvadostest.DataManagerToken
+ tok := arvadostest.SystemRootToken
// Nonexistent mount UUID
resp = s.call("GET", "/mounts/X/blocks", tok, nil)
s.remoteAPI.StartTLS()
s.cluster = testCluster(c)
s.cluster.Collections.BlobSigningKey = knownKey
- s.cluster.SystemRootToken = arvadostest.DataManagerToken
+ s.cluster.SystemRootToken = arvadostest.SystemRootToken
s.cluster.RemoteClusters = map[string]arvados.RemoteCluster{
s.remoteClusterID: arvados.RemoteCluster{
Host: strings.Split(s.remoteAPI.URL, "//")[1],
rake
BUNDLED WITH
- 2.0.2
+ 1.17.3
// srcConfig
var srcConfig apiConfig
srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- srcConfig.APIToken = arvadostest.DataManagerToken
+ srcConfig.APIToken = arvadostest.SystemRootToken
srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
// dstConfig
var dstConfig apiConfig
dstConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- dstConfig.APIToken = arvadostest.DataManagerToken
+ dstConfig.APIToken = arvadostest.SystemRootToken
dstConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
if enforcePermissions {
c.Check(err, IsNil)
c.Assert(srcConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
- c.Assert(srcConfig.APIToken, Equals, arvadostest.DataManagerToken)
+ c.Assert(srcConfig.APIToken, Equals, arvadostest.SystemRootToken)
c.Assert(srcConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
c.Assert(srcConfig.ExternalClient, Equals, false)
c.Check(err, IsNil)
c.Assert(dstConfig.APIHost, Equals, os.Getenv("ARVADOS_API_HOST"))
- c.Assert(dstConfig.APIToken, Equals, arvadostest.DataManagerToken)
+ c.Assert(dstConfig.APIToken, Equals, arvadostest.SystemRootToken)
c.Assert(dstConfig.APIHostInsecure, Equals, arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE")))
c.Assert(dstConfig.ExternalClient, Equals, false)
func (s *ServerNotRequiredSuite) TestSetupKeepClient_NoBlobSignatureTTL(c *C) {
var srcConfig apiConfig
srcConfig.APIHost = os.Getenv("ARVADOS_API_HOST")
- srcConfig.APIToken = arvadostest.DataManagerToken
+ srcConfig.APIToken = arvadostest.SystemRootToken
srcConfig.APIHostInsecure = arvadosclient.StringBool(os.Getenv("ARVADOS_API_HOST_INSECURE"))
_, ttl, err := setupKeepClient(srcConfig, srcKeepServicesJSON, false, 0, 0)
c.Check(err, IsNil)
fileContent := "ARVADOS_API_HOST=" + os.Getenv("ARVADOS_API_HOST") + "\n"
- fileContent += "ARVADOS_API_TOKEN=" + arvadostest.DataManagerToken + "\n"
+ fileContent += "ARVADOS_API_TOKEN=" + arvadostest.SystemRootToken + "\n"
fileContent += "ARVADOS_API_HOST_INSECURE=" + os.Getenv("ARVADOS_API_HOST_INSECURE") + "\n"
fileContent += "ARVADOS_EXTERNAL_CLIENT=false\n"
fileContent += "ARVADOS_BLOB_SIGNING_KEY=abcdefg"