Merge pull request #627 from butonic/add-basic-auth-option
add enable basic auth option and check permissions
This commit is contained in:
+3
-2
@@ -362,7 +362,7 @@ def localApiTests(ctx, coreBranch = 'master', coreCommit = '', storage = 'ownclo
|
||||
'OCIS_SKELETON_STRATEGY': '%s' % ('copy' if storage == 'owncloud' else 'upload'),
|
||||
'TEST_OCIS':'true',
|
||||
'BEHAT_FILTER_TAGS': '~@skipOnOcis-%s-Storage' % ('OC' if storage == 'owncloud' else 'OCIS'),
|
||||
'PATH_TO_CORE': '/srv/app/testrunner'
|
||||
'PATH_TO_CORE': '/srv/app/testrunner',
|
||||
},
|
||||
'commands': [
|
||||
'cd ocis',
|
||||
@@ -419,7 +419,7 @@ def coreApiTests(ctx, coreBranch = 'master', coreCommit = '', part_number = 1, n
|
||||
'BEHAT_FILTER_TAGS': '~@notToImplementOnOCIS&&~@toImplementOnOCIS&&~comments-app-required&&~@federation-app-required&&~@notifications-app-required&&~systemtags-app-required&&~@local_storage&&~@skipOnOcis-%s-Storage' % ('OC' if storage == 'owncloud' else 'OCIS'),
|
||||
'DIVIDE_INTO_NUM_PARTS': number_of_parts,
|
||||
'RUN_PART': part_number,
|
||||
'EXPECTED_FAILURES_FILE': '/drone/src/ocis/tests/acceptance/expected-failures-on-%s-storage.txt' % (storage.upper())
|
||||
'EXPECTED_FAILURES_FILE': '/drone/src/ocis/tests/acceptance/expected-failures-on-%s-storage.txt' % (storage.upper()),
|
||||
},
|
||||
'commands': [
|
||||
'cd /srv/app/testrunner',
|
||||
@@ -1406,6 +1406,7 @@ def ocisServer(storage):
|
||||
'STORAGE_DATAGATEWAY_PUBLIC_URL': 'https://ocis-server:9200/data',
|
||||
'STORAGE_USERS_DATA_SERVER_URL': 'http://ocis-server:9158/data',
|
||||
'STORAGE_FRONTEND_PUBLIC_URL': 'https://ocis-server:9200',
|
||||
'PROXY_ENABLE_BASIC_AUTH': True,
|
||||
'PHOENIX_WEB_CONFIG': '/drone/src/ocis/tests/config/drone/ocis-config.json',
|
||||
'KONNECTD_IDENTIFIER_REGISTRATION_CONF': '/drone/src/ocis/tests/config/drone/identifier-registration.yml',
|
||||
'KONNECTD_ISS': 'https://ocis-server:9200',
|
||||
|
||||
@@ -630,7 +630,7 @@ func TestListAccounts(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.IsType(t, &proto.ListAccountsResponse{}, resp)
|
||||
assert.Equal(t, 8, len(resp.Accounts))
|
||||
assert.Equal(t, 9, len(resp.Accounts))
|
||||
|
||||
assertResponseContainsUser(t, resp, getAccount("user1"))
|
||||
assertResponseContainsUser(t, resp, getAccount("user2"))
|
||||
@@ -642,8 +642,8 @@ func TestListWithoutUserCreation(t *testing.T) {
|
||||
resp, err := listAccounts(t)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Only 5 default users
|
||||
assert.Equal(t, 6, len(resp.Accounts))
|
||||
// Only 7 default users
|
||||
assert.Equal(t, 7, len(resp.Accounts))
|
||||
cleanUp(t)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@ import (
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
fieldmask_utils "github.com/mennanov/fieldmask-utils"
|
||||
merrors "github.com/micro/go-micro/v2/errors"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/owncloud/ocis/accounts/pkg/proto/v0"
|
||||
"github.com/owncloud/ocis/accounts/pkg/storage"
|
||||
"github.com/owncloud/ocis/ocis-pkg/middleware"
|
||||
"github.com/owncloud/ocis/ocis-pkg/roles"
|
||||
settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
|
||||
settings_svc "github.com/owncloud/ocis/settings/pkg/service/v0"
|
||||
@@ -64,10 +66,14 @@ func (s Service) hasAccountManagementPermissions(ctx context.Context) bool {
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(ctx)
|
||||
if !ok {
|
||||
/**
|
||||
* FIXME: with this we are skipping permission checks on all requests that are coming in without roleIDs in the
|
||||
* metadata context. This is a huge security impairment, as that's the case not only for grpc requests but also
|
||||
* for unauthenticated http requests and http requests coming in without hitting the ocis-proxy first.
|
||||
* FIXME: with this we are skipping permission checks on all requests that are coming in without roleIDs in the
|
||||
* metadata context. This is a huge security impairment, as that's the case not only for grpc requests but also
|
||||
* for unauthenticated http requests and http requests coming in without hitting the ocis-proxy first.
|
||||
*/
|
||||
// TODO add system role for internal requests.
|
||||
// - at least the proxy needs to look up account info
|
||||
// - glauth needs to make bind requests
|
||||
// tracked as OCIS-454
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -75,6 +81,17 @@ func (s Service) hasAccountManagementPermissions(ctx context.Context) bool {
|
||||
return s.RoleManager.FindPermissionByID(ctx, roleIDs, AccountManagementPermissionID) != nil
|
||||
}
|
||||
|
||||
func (s Service) hasSelfManagementPermissions(ctx context.Context) bool {
|
||||
// get roles from context
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(ctx)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// check if permission is present in roles of the authenticated account
|
||||
return s.RoleManager.FindPermissionByID(ctx, roleIDs, SelfManagementPermissionID) != nil
|
||||
}
|
||||
|
||||
// serviceUserToIndex temporarily adds a service user to the index, which is supposed to be removed before the lock on the handler function is released
|
||||
func (s Service) serviceUserToIndex() (teardownServiceUser func()) {
|
||||
if s.Config.ServiceUser.Username != "" && s.Config.ServiceUser.UUID != "" {
|
||||
@@ -105,9 +122,12 @@ func (s Service) getInMemoryServiceUser() proto.Account {
|
||||
// ListAccounts implements the AccountsServiceHandler interface
|
||||
// the query contains account properties
|
||||
func (s Service) ListAccounts(ctx context.Context, in *proto.ListAccountsRequest, out *proto.ListAccountsResponse) (err error) {
|
||||
if !s.hasAccountManagementPermissions(ctx) {
|
||||
hasSelf := s.hasSelfManagementPermissions(ctx)
|
||||
hasManagement := s.hasAccountManagementPermissions(ctx)
|
||||
if !hasSelf && !hasManagement {
|
||||
return merrors.Forbidden(s.id, "no permission for ListAccounts")
|
||||
}
|
||||
onlySelf := hasSelf && !hasManagement
|
||||
|
||||
accLock.Lock()
|
||||
defer accLock.Unlock()
|
||||
@@ -146,6 +166,15 @@ func (s Service) ListAccounts(ctx context.Context, in *proto.ListAccountsRequest
|
||||
return nil
|
||||
}
|
||||
|
||||
if onlySelf {
|
||||
// limit list to own account id
|
||||
if aid, ok := metadata.Get(ctx, middleware.AccountID); ok {
|
||||
in.Query = "id eq '" + aid + "'"
|
||||
} else {
|
||||
return merrors.InternalServerError(s.id, "account id not in context")
|
||||
}
|
||||
}
|
||||
|
||||
if in.Query == "" {
|
||||
err = s.repo.LoadAccounts(ctx, &out.Accounts)
|
||||
if err != nil {
|
||||
@@ -202,9 +231,12 @@ func (s Service) findAccountsByQuery(ctx context.Context, query string) ([]strin
|
||||
|
||||
// GetAccount implements the AccountsServiceHandler interface
|
||||
func (s Service) GetAccount(ctx context.Context, in *proto.GetAccountRequest, out *proto.Account) (err error) {
|
||||
if !s.hasAccountManagementPermissions(ctx) {
|
||||
hasSelf := s.hasSelfManagementPermissions(ctx)
|
||||
hasManagement := s.hasAccountManagementPermissions(ctx)
|
||||
if !hasSelf && !hasManagement {
|
||||
return merrors.Forbidden(s.id, "no permission for GetAccount")
|
||||
}
|
||||
onlySelf := hasSelf && !hasManagement
|
||||
|
||||
accLock.Lock()
|
||||
defer accLock.Unlock()
|
||||
@@ -213,6 +245,17 @@ func (s Service) GetAccount(ctx context.Context, in *proto.GetAccountRequest, ou
|
||||
return merrors.InternalServerError(s.id, "could not clean up account id: %v", err.Error())
|
||||
}
|
||||
|
||||
if onlySelf {
|
||||
// limit get to own account id
|
||||
if aid, ok := metadata.Get(ctx, middleware.AccountID); ok {
|
||||
if id != aid {
|
||||
return merrors.Forbidden(s.id, "no permission for GetAccount of another user")
|
||||
}
|
||||
} else {
|
||||
return merrors.InternalServerError(s.id, "account id not in context")
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.repo.LoadAccount(ctx, id, out); err != nil {
|
||||
if storage.IsNotFoundErr(err) {
|
||||
return merrors.NotFound(s.id, "account not found: %v", err.Error())
|
||||
@@ -268,7 +311,7 @@ func (s Service) CreateAccount(ctx context.Context, in *proto.CreateAccountReque
|
||||
return merrors.InternalServerError(s.id, "could not check if account exists: %v", err.Error())
|
||||
}
|
||||
if exists {
|
||||
return merrors.BadRequest(s.id, "account already exists")
|
||||
return merrors.Conflict(s.id, "account already exists")
|
||||
}
|
||||
|
||||
if out.PasswordProfile != nil {
|
||||
@@ -298,7 +341,7 @@ func (s Service) CreateAccount(ctx context.Context, in *proto.CreateAccountReque
|
||||
indexResults, err := s.index.Add(out)
|
||||
if err != nil {
|
||||
s.rollbackCreateAccount(ctx, out)
|
||||
return merrors.BadRequest(s.id, "Account already exists %v", err.Error())
|
||||
return merrors.Conflict(s.id, "Account already exists %v", err.Error())
|
||||
|
||||
}
|
||||
s.log.Debug().Interface("account", out).Msg("account after indexing")
|
||||
@@ -370,9 +413,12 @@ func (s Service) rollbackCreateAccount(ctx context.Context, acc *proto.Account)
|
||||
// read only fields are ignored
|
||||
// TODO how can we unset specific values? using the update mask
|
||||
func (s Service) UpdateAccount(ctx context.Context, in *proto.UpdateAccountRequest, out *proto.Account) (err error) {
|
||||
if !s.hasAccountManagementPermissions(ctx) {
|
||||
hasSelf := s.hasSelfManagementPermissions(ctx)
|
||||
hasManagement := s.hasAccountManagementPermissions(ctx)
|
||||
if !hasSelf && !hasManagement {
|
||||
return merrors.Forbidden(s.id, "no permission for UpdateAccount")
|
||||
}
|
||||
onlySelf := hasSelf && !hasManagement
|
||||
|
||||
accLock.Lock()
|
||||
defer accLock.Unlock()
|
||||
@@ -388,6 +434,17 @@ func (s Service) UpdateAccount(ctx context.Context, in *proto.UpdateAccountReque
|
||||
return merrors.InternalServerError(s.id, "could not clean up account id: %v", err.Error())
|
||||
}
|
||||
|
||||
if onlySelf {
|
||||
// limit update to own account id
|
||||
if aid, ok := metadata.Get(ctx, middleware.AccountID); ok {
|
||||
if id != aid {
|
||||
return merrors.Forbidden(s.id, "no permission to UpdateAccount of another user")
|
||||
}
|
||||
} else {
|
||||
return merrors.InternalServerError(s.id, "account id not in context")
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.repo.LoadAccount(ctx, id, out); err != nil {
|
||||
if storage.IsNotFoundErr(err) {
|
||||
return merrors.NotFound(s.id, "account not found: %v", err.Error())
|
||||
@@ -395,7 +452,6 @@ func (s Service) UpdateAccount(ctx context.Context, in *proto.UpdateAccountReque
|
||||
|
||||
s.log.Error().Err(err).Str("id", id).Msg("could not load account")
|
||||
return merrors.InternalServerError(s.id, "could not load account: %v", err.Error())
|
||||
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
@@ -404,9 +460,15 @@ func (s Service) UpdateAccount(ctx context.Context, in *proto.UpdateAccountReque
|
||||
Nanos: int32(t.Nanosecond()),
|
||||
}
|
||||
|
||||
validMask, err := validateUpdate(in.UpdateMask, updatableAccountPaths)
|
||||
if err != nil {
|
||||
return merrors.BadRequest(s.id, "%s", err)
|
||||
var validMask fieldmask_utils.FieldFilterContainer
|
||||
if onlySelf {
|
||||
if validMask, err = validateUpdate(in.UpdateMask, selfUpdatableAccountPaths); err != nil {
|
||||
return merrors.BadRequest(s.id, "%s", err)
|
||||
}
|
||||
} else {
|
||||
if validMask, err = validateUpdate(in.UpdateMask, updatableAccountPaths); err != nil {
|
||||
return merrors.BadRequest(s.id, "%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, exists := validMask.Filter("PreferredName"); exists {
|
||||
@@ -490,6 +552,14 @@ func (s Service) UpdateAccount(ctx context.Context, in *proto.UpdateAccountReque
|
||||
return
|
||||
}
|
||||
|
||||
// whitelist of all paths/fields which can be updated by users themself
|
||||
var selfUpdatableAccountPaths = map[string]struct{}{
|
||||
"DisplayName": {},
|
||||
"Description": {},
|
||||
"Mail": {}, // read only?,
|
||||
"PasswordProfile.Password": {},
|
||||
}
|
||||
|
||||
// whitelist of all paths/fields which can be updated by clients
|
||||
var updatableAccountPaths = map[string]struct{}{
|
||||
"AccountEnabled": {},
|
||||
|
||||
@@ -50,25 +50,30 @@ func (s Service) deflateMembers(g *proto.Group) {
|
||||
}
|
||||
|
||||
// ListGroups implements the GroupsServiceHandler interface
|
||||
func (s Service) ListGroups(c context.Context, in *proto.ListGroupsRequest, out *proto.ListGroupsResponse) (err error) {
|
||||
var searchResults []string
|
||||
|
||||
out.Groups = make([]*proto.Group, 0)
|
||||
func (s Service) ListGroups(ctx context.Context, in *proto.ListGroupsRequest, out *proto.ListGroupsResponse) (err error) {
|
||||
if in.Query == "" {
|
||||
searchResults, _ = s.index.FindByPartial(&proto.Group{}, "DisplayName", "*")
|
||||
err = s.repo.LoadGroups(ctx, &out.Groups)
|
||||
if err != nil {
|
||||
s.log.Err(err).Msg("failed to load all groups from storage")
|
||||
return merrors.InternalServerError(s.id, "failed to load all groups")
|
||||
}
|
||||
for i := range out.Groups {
|
||||
a := out.Groups[i]
|
||||
|
||||
// TODO add accounts only if requested
|
||||
// if in.FieldMask ...
|
||||
s.expandMembers(a)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
var startsWithIDQuery = regexp.MustCompile(`^startswith\(id,'(.*)'\)$`)
|
||||
match := startsWithIDQuery.FindStringSubmatch(in.Query)
|
||||
if len(match) == 2 {
|
||||
searchResults = []string{match[1]}
|
||||
}
|
||||
*/
|
||||
searchResults, err := s.findGroupsByQuery(ctx, in.Query)
|
||||
out.Groups = make([]*proto.Group, 0, len(searchResults))
|
||||
|
||||
for _, hit := range searchResults {
|
||||
g := &proto.Group{}
|
||||
if err = s.repo.LoadGroup(c, hit, g); err != nil {
|
||||
if err = s.repo.LoadGroup(ctx, hit, g); err != nil {
|
||||
s.log.Error().Err(err).Str("group", hit).Msg("could not load group, skipping")
|
||||
continue
|
||||
}
|
||||
@@ -83,6 +88,9 @@ func (s Service) ListGroups(c context.Context, in *proto.ListGroupsRequest, out
|
||||
|
||||
return
|
||||
}
|
||||
func (s Service) findGroupsByQuery(ctx context.Context, query string) ([]string, error) {
|
||||
return s.index.Query(&proto.Group{}, query)
|
||||
}
|
||||
|
||||
// GetGroup implements the GroupsServiceHandler interface
|
||||
func (s Service) GetGroup(c context.Context, in *proto.GetGroupRequest, out *proto.Group) (err error) {
|
||||
@@ -249,8 +257,11 @@ func (s Service) AddMember(c context.Context, in *proto.AddMemberRequest, out *p
|
||||
alreadyRelated = true
|
||||
}
|
||||
}
|
||||
aref := &proto.Account{
|
||||
Id: a.Id,
|
||||
}
|
||||
if !alreadyRelated {
|
||||
g.Members = append(g.Members, a)
|
||||
g.Members = append(g.Members, aref)
|
||||
}
|
||||
|
||||
// check if we need to add the group to the account
|
||||
@@ -261,8 +272,12 @@ func (s Service) AddMember(c context.Context, in *proto.AddMemberRequest, out *p
|
||||
break
|
||||
}
|
||||
}
|
||||
// only store the reference to prevent recurision when marshaling json
|
||||
gref := &proto.Group{
|
||||
Id: g.Id,
|
||||
}
|
||||
if !alreadyRelated {
|
||||
a.MemberOf = append(a.MemberOf, g)
|
||||
a.MemberOf = append(a.MemberOf, gref)
|
||||
}
|
||||
|
||||
if err = s.repo.WriteAccount(c, a); err != nil {
|
||||
|
||||
@@ -11,13 +11,17 @@ import (
|
||||
|
||||
const (
|
||||
// AccountManagementPermissionID is the hardcoded setting UUID for the account management permission
|
||||
AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73"
|
||||
AccountManagementPermissionID string = "8e587774-d929-4215-910b-a317b1e80f73"
|
||||
// AccountManagementPermissionName is the hardcoded setting name for the account management permission
|
||||
AccountManagementPermissionName string = "account-management"
|
||||
// GroupManagementPermissionID is the hardcoded setting UUID for the group management permission
|
||||
GroupManagementPermissionID string = "522adfbe-5908-45b4-b135-41979de73245"
|
||||
GroupManagementPermissionID string = "522adfbe-5908-45b4-b135-41979de73245"
|
||||
// GroupManagementPermissionName is the hardcoded setting name for the group management permission
|
||||
GroupManagementPermissionName string = "group-management"
|
||||
GroupManagementPermissionName string = "group-management"
|
||||
// SelfManagementPermissionID is the hardcoded setting UUID for the self management permission
|
||||
SelfManagementPermissionID string = "e03070e9-4362-4cc6-a872-1c7cb2eb2b8e"
|
||||
// SelfManagementPermissionName is the hardcoded setting name for the self management permission
|
||||
SelfManagementPermissionName string = "self-management"
|
||||
)
|
||||
|
||||
// RegisterPermissions registers permissions for account management and group management with the settings service.
|
||||
@@ -78,5 +82,24 @@ func generateAccountManagementPermissionsRequests() []settings.AddSettingToBundl
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
BundleId: ssvc.BundleUUIDRoleUser,
|
||||
Setting: &settings.Setting{
|
||||
Id: SelfManagementPermissionID,
|
||||
Name: SelfManagementPermissionName,
|
||||
DisplayName: "Self Management",
|
||||
Description: "This permission gives access to self management.",
|
||||
Resource: &settings.Resource{
|
||||
Type: settings.Resource_TYPE_USER,
|
||||
Id: "me",
|
||||
},
|
||||
Value: &settings.Setting_PermissionValue{
|
||||
PermissionValue: &settings.Permission{
|
||||
Operation: settings.Permission_OPERATION_READWRITE,
|
||||
Constraint: settings.Permission_CONSTRAINT_OWN,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +218,22 @@ func (s Service) createDefaultAccounts() (err error) {
|
||||
{Id: "509a9dcd-bb37-4f4f-a01a-19dca27d9cfa"}, // users
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "ddc2004c-0977-11eb-9d3f-a793888cd0f8",
|
||||
PreferredName: "admin",
|
||||
OnPremisesSamAccountName: "admin",
|
||||
Mail: "admin@example.org",
|
||||
DisplayName: "Admin",
|
||||
UidNumber: 20004,
|
||||
GidNumber: 30000,
|
||||
PasswordProfile: &proto.PasswordProfile{
|
||||
Password: "$6$rounds=95551$/bdqsmiGleA20kAS$rCAvHV7wjaHVF5nEVAnpW7mugRqcnPmdU4UPqhSroE74gXFxNGZflCF.ZyHwocDwgAw3uLkqsCzB1h5bXBjYB0",
|
||||
},
|
||||
AccountEnabled: true,
|
||||
MemberOf: []*proto.Group{
|
||||
{Id: "509a9dcd-bb37-4f4f-a01a-19dca27d9cfa"}, // users
|
||||
},
|
||||
},
|
||||
// technical users for kopano and reva
|
||||
{
|
||||
Id: "820ba2a1-3f54-4538-80a4-2d73007e30bf",
|
||||
@@ -293,6 +309,9 @@ func (s Service) createDefaultAccounts() (err error) {
|
||||
assignRoleToUser("058bff95-6708-4fe5-91e4-9ea3d377588b", settings_svc.BundleUUIDRoleAdmin, s.RoleService, s.log)
|
||||
for _, accountID := range []string{
|
||||
"058bff95-6708-4fe5-91e4-9ea3d377588b", //moss
|
||||
"ddc2004c-0977-11eb-9d3f-a793888cd0f8", //admin
|
||||
"820ba2a1-3f54-4538-80a4-2d73007e30bf", //konnectd
|
||||
"bc596f3c-c955-4328-80a0-60d018b4ad57", //reva
|
||||
} {
|
||||
assignRoleToUser(accountID, settings_svc.BundleUUIDRoleAdmin, s.RoleService, s.log)
|
||||
}
|
||||
|
||||
@@ -73,15 +73,19 @@ func (r DiskRepo) LoadAccount(ctx context.Context, id string, a *proto.Account)
|
||||
// LoadAccounts loads all the accounts from the local filesystem
|
||||
func (r DiskRepo) LoadAccounts(ctx context.Context, a *[]*proto.Account) (err error) {
|
||||
root := filepath.Join(r.cfg.Repo.Disk.Path, accountsFolder)
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
infos, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range infos {
|
||||
acc := &proto.Account{}
|
||||
if e := r.LoadAccount(ctx, filepath.Base(path), acc); e != nil {
|
||||
if e := r.LoadAccount(ctx, infos[i].Name(), acc); e != nil {
|
||||
r.log.Err(e).Msg("could not load account")
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
*a = append(*a, acc)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAccount from the local filesystem
|
||||
@@ -135,15 +139,19 @@ func (r DiskRepo) LoadGroup(ctx context.Context, id string, g *proto.Group) (err
|
||||
// LoadGroups loads all the groups from the local filesystem
|
||||
func (r DiskRepo) LoadGroups(ctx context.Context, g *[]*proto.Group) (err error) {
|
||||
root := filepath.Join(r.cfg.Repo.Disk.Path, groupsFolder)
|
||||
return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||
infos, err := ioutil.ReadDir(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range infos {
|
||||
grp := &proto.Group{}
|
||||
if e := r.LoadGroup(ctx, filepath.Base(path), grp); e != nil {
|
||||
if e := r.LoadGroup(ctx, infos[i].Name(), grp); e != nil {
|
||||
r.log.Err(e).Msg("could not load group")
|
||||
return nil
|
||||
continue
|
||||
}
|
||||
*g = append(*g, grp)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteGroup from the local filesystem
|
||||
|
||||
@@ -18,11 +18,13 @@ Feature: Accounts
|
||||
When the user reloads the current page of the webUI
|
||||
Then the displayed role of user "einstein" should be "Admin" on the WebUI
|
||||
|
||||
@skip @issue-product-167
|
||||
Scenario: regular user should not be able to see accounts list
|
||||
Given user "Marie" has logged in using the webUI
|
||||
When the user browses to the accounts page
|
||||
Then the user should not be able to see the accounts list on the WebUI
|
||||
|
||||
@skip @issue-product-167
|
||||
Scenario: guest user should not be able to see accounts list
|
||||
Given user "Moss" has logged in using the webUI
|
||||
When the user browses to the accounts page
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Enhancement: Add basic auth option
|
||||
|
||||
We added a new `enable-basic-auth` option and `PROXY_ENABLE_BASIC_AUTH` environment variable that can be set to `true` to make the proxy verify the basic auth header with the accounts service. This should only be used for testing and development and is disabled by default.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/627
|
||||
https://github.com/owncloud/product/issues/198
|
||||
@@ -29,9 +29,11 @@ File versions need a redis server. Start one with docker by using:
|
||||
|
||||
To start ocis:
|
||||
```
|
||||
bin/ocis server
|
||||
PROXY_ENABLE_BASIC_AUTH=true bin/ocis server
|
||||
```
|
||||
|
||||
`PROXY_ENABLE_BASIC_AUTH` will allow the acceptance tests to make requests against the provisioning api (and other endpoints) using basic auth.
|
||||
|
||||
### Run the acceptance tests
|
||||
First we will need to clone the testing app in owncloud which contains the skeleton files required for running the tests.
|
||||
In the ownCloud 10 core clone the testing app with the following command:
|
||||
|
||||
@@ -91,9 +91,10 @@ marie:radioactivity
|
||||
richard:superfluidity
|
||||
```
|
||||
|
||||
There is an admin demo account:
|
||||
There are admin demo accounts:
|
||||
```console
|
||||
moss:vista
|
||||
admin:admin
|
||||
```
|
||||
|
||||
## Runtime
|
||||
|
||||
@@ -55,6 +55,8 @@ github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c/go.mod h1:chxPXzS
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/CiscoM31/godata v0.0.0-20201003040028-eadcd34e7f06 h1:FKxVU/j9Dd8Je0YkVkm8Fxpz9zIeN21SEkcbzA6NWgY=
|
||||
github.com/CiscoM31/godata v0.0.0-20201003040028-eadcd34e7f06/go.mod h1:tjaihnMBH6p5DVnGBksDQQHpErbrLvb9ek6cEWuyc7E=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e h1:Bqtt5C+uVk+vH/t5dmB47uDCTwxw16EYHqvJnmY2aQc=
|
||||
github.com/GeertJohan/yubigo v0.0.0-20190917122436-175bc097e60e/go.mod h1:njRCDrl+1RQ/A/+KVU8Ho2EWAxUSkohOWczdW3dzDG0=
|
||||
|
||||
@@ -200,6 +200,7 @@ func Server(cfg *config.Config) *cli.Command {
|
||||
glauth.LDAPS(&lscfg),
|
||||
glauth.Backend(&bcfg),
|
||||
glauth.Fallback(&fcfg),
|
||||
glauth.RoleBundleUUID(cfg.RoleBundleUUID),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
|
||||
+11
-10
@@ -58,16 +58,17 @@ type Backend struct {
|
||||
|
||||
// Config combines all available configuration parts.
|
||||
type Config struct {
|
||||
File string
|
||||
Log Log
|
||||
Debug Debug
|
||||
HTTP HTTP
|
||||
Tracing Tracing
|
||||
Ldap Ldap
|
||||
Ldaps Ldaps
|
||||
Backend Backend
|
||||
Fallback Backend
|
||||
Version string
|
||||
File string
|
||||
Log Log
|
||||
Debug Debug
|
||||
HTTP HTTP
|
||||
Tracing Tracing
|
||||
Ldap Ldap
|
||||
Ldaps Ldaps
|
||||
Backend Backend
|
||||
Fallback Backend
|
||||
Version string
|
||||
RoleBundleUUID string
|
||||
}
|
||||
|
||||
// New initializes a new configuration with or without defaults.
|
||||
|
||||
@@ -115,6 +115,13 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
EnvVars: []string{"GLAUTH_DEBUG_ZPAGES"},
|
||||
Destination: &cfg.Debug.Zpages,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "role-bundle-id",
|
||||
Value: "71881883-1768-46bd-a24d-a356a2afdf7f", // BundleUUIDRoleAdmin
|
||||
Usage: "roleid used to make internal grpc requests",
|
||||
EnvVars: []string{"GLAUTH_ROLE_BUNDLE_ID"},
|
||||
Destination: &cfg.RoleBundleUUID,
|
||||
},
|
||||
|
||||
&cli.StringFlag{
|
||||
Name: "ldap-addr",
|
||||
|
||||
@@ -2,6 +2,7 @@ package glauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@@ -9,10 +10,12 @@ import (
|
||||
|
||||
"github.com/glauth/glauth/pkg/handler"
|
||||
"github.com/glauth/glauth/pkg/stats"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
ber "github.com/nmcclain/asn1-ber"
|
||||
"github.com/nmcclain/ldap"
|
||||
accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/ocis-pkg/middleware"
|
||||
)
|
||||
|
||||
type queryType string
|
||||
@@ -29,6 +32,7 @@ type ocisHandler struct {
|
||||
basedn string
|
||||
nameFormat string
|
||||
groupFormat string
|
||||
rbid string
|
||||
}
|
||||
|
||||
func (h ocisHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAPResultCode, error) {
|
||||
@@ -66,8 +70,22 @@ func (h ocisHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAP
|
||||
}
|
||||
userName := strings.TrimPrefix(parts[0], "cn=")
|
||||
|
||||
// TODO make glauth context aware
|
||||
ctx := context.Background()
|
||||
|
||||
// use a session with the bound user?
|
||||
roleIDs, err := json.Marshal([]string{h.rbid})
|
||||
if err != nil {
|
||||
h.log.Error().
|
||||
Err(err).
|
||||
Str("handler", "ocis").
|
||||
Msg("could not marshal roleid json")
|
||||
return ldap.LDAPResultOperationsError, nil
|
||||
}
|
||||
ctx = metadata.Set(ctx, middleware.RoleIDs, string(roleIDs))
|
||||
|
||||
// check password
|
||||
res, err := h.as.ListAccounts(context.TODO(), &accounts.ListAccountsRequest{
|
||||
res, err := h.as.ListAccounts(ctx, &accounts.ListAccountsRequest{
|
||||
//Query: fmt.Sprintf("username eq '%s'", username),
|
||||
// TODO this allows lookung up users when you know the username using basic auth
|
||||
// adding the password to the query is an option but sending the sover the wira a la scim seems ugly
|
||||
@@ -76,6 +94,7 @@ func (h ocisHandler) Bind(bindDN, bindSimplePw string, conn net.Conn) (ldap.LDAP
|
||||
})
|
||||
if err != nil || len(res.Accounts) == 0 {
|
||||
h.log.Error().
|
||||
Err(err).
|
||||
Str("handler", "ocis").
|
||||
Str("username", userName).
|
||||
Str("binddn", bindDN).
|
||||
@@ -162,6 +181,22 @@ func (h ocisHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn ne
|
||||
}
|
||||
}
|
||||
|
||||
// TODO make glauth context aware
|
||||
ctx := context.Background()
|
||||
|
||||
// use a session with the bound user?
|
||||
roleIDs, err := json.Marshal([]string{h.rbid})
|
||||
if err != nil {
|
||||
h.log.Error().
|
||||
Err(err).
|
||||
Str("handler", "ocis").
|
||||
Msg("could not marshal roleid json")
|
||||
return ldap.ServerSearchResult{
|
||||
ResultCode: ldap.LDAPResultOperationsError,
|
||||
}, nil
|
||||
}
|
||||
ctx = metadata.Set(ctx, middleware.RoleIDs, string(roleIDs))
|
||||
|
||||
entries := []*ldap.Entry{}
|
||||
h.log.Debug().
|
||||
Str("handler", "ocis").
|
||||
@@ -173,7 +208,7 @@ func (h ocisHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn ne
|
||||
Msg("parsed query")
|
||||
switch qtype {
|
||||
case usersQuery:
|
||||
accounts, err := h.as.ListAccounts(context.TODO(), &accounts.ListAccountsRequest{
|
||||
accounts, err := h.as.ListAccounts(ctx, &accounts.ListAccountsRequest{
|
||||
Query: query,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -193,7 +228,7 @@ func (h ocisHandler) Search(bindDN string, searchReq ldap.SearchRequest, conn ne
|
||||
}
|
||||
entries = append(entries, h.mapAccounts(accounts.Accounts)...)
|
||||
case groupsQuery:
|
||||
groups, err := h.gs.ListGroups(context.TODO(), &accounts.ListGroupsRequest{
|
||||
groups, err := h.gs.ListGroups(ctx, &accounts.ListGroupsRequest{
|
||||
Query: query,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -501,6 +536,7 @@ func NewOCISHandler(opts ...Option) handler.Handler {
|
||||
basedn: options.BaseDN,
|
||||
nameFormat: options.NameFormat,
|
||||
groupFormat: options.GroupFormat,
|
||||
rbid: options.RoleBundleUUID,
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type Options struct {
|
||||
BaseDN string
|
||||
NameFormat string
|
||||
GroupFormat string
|
||||
RoleBundleUUID string
|
||||
AccountsService accounts.AccountsService
|
||||
GroupsService accounts.GroupsService
|
||||
}
|
||||
@@ -113,3 +114,10 @@ func GroupsService(val accounts.GroupsService) Option {
|
||||
o.GroupsService = val
|
||||
}
|
||||
}
|
||||
|
||||
// RoleBundleUUID provides a role bundle UUID to make internal grpc requests.
|
||||
func RoleBundleUUID(val string) Option {
|
||||
return func(o *Options) {
|
||||
o.RoleBundleUUID = val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ func Server(opts ...Option) (*LdapSvc, error) {
|
||||
BaseDN(s.backend.Backend.BaseDN),
|
||||
NameFormat(s.backend.Backend.NameFormat),
|
||||
GroupFormat(s.backend.Backend.GroupFormat),
|
||||
RoleBundleUUID(options.RoleBundleUUID),
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported backend %s - must be 'ldap', 'owncloud' or 'accounts'", s.backend.Backend.Datastore)
|
||||
@@ -115,6 +116,7 @@ func Server(opts ...Option) (*LdapSvc, error) {
|
||||
BaseDN(s.fallback.Backend.BaseDN),
|
||||
NameFormat(s.fallback.Backend.NameFormat),
|
||||
GroupFormat(s.fallback.Backend.GroupFormat),
|
||||
RoleBundleUUID(options.RoleBundleUUID),
|
||||
)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported fallback %s - must be 'ldap', 'owncloud' or 'accounts'", s.fallback.Backend.Datastore)
|
||||
|
||||
@@ -196,6 +196,9 @@ func (k Konnectd) Index() http.HandlerFunc {
|
||||
if err != nil {
|
||||
k.logger.Fatal().Err(err).Msg("Could not read index template")
|
||||
}
|
||||
if err = f.Close(); err != nil {
|
||||
k.logger.Fatal().Err(err).Msg("Could not close body")
|
||||
}
|
||||
|
||||
// TODO add environment variable to make the path prefix configurable
|
||||
pp := "/signin/v1"
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/pkg/token/manager/jwt"
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
"github.com/micro/go-micro/v2/metadata"
|
||||
"github.com/owncloud/ocis/ocis-pkg/account"
|
||||
)
|
||||
@@ -49,18 +50,21 @@ func ExtractAccountUUID(opts ...account.Option) func(http.Handler) http.Handler
|
||||
return
|
||||
}
|
||||
|
||||
user, err := tokenManager.DismantleToken(r.Context(), token)
|
||||
u, err := tokenManager.DismantleToken(r.Context(), token)
|
||||
if err != nil {
|
||||
opt.Logger.Error().Err(err)
|
||||
return
|
||||
}
|
||||
|
||||
// store user in context for request
|
||||
ctx := user.ContextSetUser(r.Context(), u)
|
||||
|
||||
// Important: user.Id.OpaqueId is the AccountUUID. Set this way in the account uuid middleware in ocis-proxy.
|
||||
// https://github.com/owncloud/ocis-proxy/blob/ea254d6036592cf9469d757d1295e0c4309d1e63/pkg/middleware/account_uuid.go#L109
|
||||
ctx := context.WithValue(r.Context(), UUIDKey, user.Id.OpaqueId)
|
||||
ctx = context.WithValue(ctx, UUIDKey, u.Id.OpaqueId)
|
||||
// TODO: implement token manager in cs3org/reva that uses generic metadata instead of access token from header.
|
||||
ctx = metadata.Set(ctx, AccountID, user.Id.OpaqueId)
|
||||
ctx = metadata.Set(ctx, RoleIDs, string(user.Opaque.Map["roles"].Value))
|
||||
ctx = metadata.Set(ctx, AccountID, u.Id.OpaqueId)
|
||||
ctx = metadata.Set(ctx, RoleIDs, string(u.Opaque.Map["roles"].Value))
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -202,18 +202,12 @@ apiProvisioning-v2/apiProvisioningUsingAppPassword.feature:67
|
||||
# https://github.com/owncloud/ocis-ocs/issues/28
|
||||
# disable users /cloud/users/disable|enable not available
|
||||
#
|
||||
apiProvisioning-v1/disableUser.feature:11
|
||||
apiProvisioning-v1/disableUser.feature:79
|
||||
apiProvisioning-v1/disableUser.feature:99
|
||||
apiProvisioning-v1/disableUser.feature:107
|
||||
apiProvisioning-v1/disableUser.feature:129
|
||||
apiProvisioning-v1/enableUser.feature:11
|
||||
apiProvisioning-v2/disableUser.feature:11
|
||||
apiProvisioning-v2/disableUser.feature:79
|
||||
apiProvisioning-v2/disableUser.feature:99
|
||||
apiProvisioning-v2/disableUser.feature:108
|
||||
apiProvisioning-v2/disableUser.feature:130
|
||||
apiProvisioning-v2/enableUser.feature:11
|
||||
#
|
||||
# https://github.com/owncloud/ocis-ocs/issues/51
|
||||
# displayname of user can be changed to empty
|
||||
@@ -226,9 +220,6 @@ apiProvisioning-v2/editUser.feature:47
|
||||
#
|
||||
apiProvisioning-v1/editUser.feature:56
|
||||
apiProvisioning-v1/editUser.feature:122
|
||||
apiProvisioning-v1/enableUser.feature:34
|
||||
apiProvisioning-v1/enableUser.feature:56
|
||||
apiProvisioning-v1/enableUser.feature:63
|
||||
apiProvisioning-v2/editUser.feature:56
|
||||
apiProvisioning-v2/editUser.feature:122
|
||||
apiProvisioning-v2/enableUser.feature:34
|
||||
@@ -238,14 +229,10 @@ apiProvisioning-v2/enableUser.feature:64
|
||||
# https://github.com/owncloud/product/issues/248
|
||||
# user can get info of other users/ cloud/users endpoints not authenticated
|
||||
#
|
||||
apiProvisioning-v1/deleteUser.feature:53
|
||||
apiProvisioning-v2/deleteUser.feature:54
|
||||
apiProvisioning-v1/getUser.feature:81
|
||||
apiProvisioning-v1/getUsers.feature:43
|
||||
apiProvisioning-v1/resetUserPassword.feature:56
|
||||
apiProvisioning-v2/getUser.feature:82
|
||||
apiProvisioning-v2/getUsers.feature:44
|
||||
apiProvisioning-v2/resetUserPassword.feature:56
|
||||
#
|
||||
# https://github.com/owncloud/product/issues/250
|
||||
# incorrect ocs(v2) status value when getting info of user that does not exist should be 404, gives 998
|
||||
@@ -315,7 +302,6 @@ apiSharees/sharees.feature:538
|
||||
# https://github.com/owncloud/ocis-reva/issues/34 groups endpoint does not exist
|
||||
#
|
||||
apiShareManagementToShares/acceptShares.feature:22
|
||||
apiShareManagementToShares/acceptShares.feature:52
|
||||
apiShareManagementToShares/acceptShares.feature:71
|
||||
apiShareManagementToShares/acceptShares.feature:156
|
||||
apiShareManagementToShares/acceptShares.feature:157
|
||||
@@ -331,12 +317,9 @@ apiShareManagementToShares/acceptShares.feature:249
|
||||
apiShareManagementToShares/acceptShares.feature:270
|
||||
apiShareManagementToShares/acceptShares.feature:279
|
||||
apiShareManagementToShares/acceptShares.feature:298
|
||||
apiShareManagementToShares/acceptShares.feature:320
|
||||
apiShareManagementToShares/acceptShares.feature:342
|
||||
apiShareManagementToShares/acceptShares.feature:378
|
||||
apiShareManagementToShares/acceptShares.feature:398
|
||||
apiShareManagementToShares/acceptShares.feature:417
|
||||
apiShareManagementToShares/acceptShares.feature:439
|
||||
#
|
||||
# https://github.com/owncloud/product/issues/207 Response is empty when accepting a share
|
||||
#
|
||||
@@ -401,12 +384,8 @@ apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.fe
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:70
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:97
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:98
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:115
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:116
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:135
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:136
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:153
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:154
|
||||
#
|
||||
# https://github.com/owncloud/ocis/issues/720 shares are mounted into /Shares folder even after the sharer deletes the collaborator
|
||||
# https://github.com/owncloud/ocis/issues/721 deleting share response does not contain `data` field
|
||||
@@ -451,11 +430,6 @@ apiShareOperationsToShares/accessToShare.feature:56
|
||||
apiShareOperationsToShares/gettingShares.feature:24
|
||||
apiShareOperationsToShares/gettingShares.feature:25
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/65 There is no such thing like a "super-user"
|
||||
#
|
||||
apiShareOperationsToShares/gettingShares.feature:38
|
||||
apiShareOperationsToShares/gettingShares.feature:39
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/357 Delete shares from user when user is deleted
|
||||
# https://github.com/owncloud/ocis-reva/issues/301 no displayname_owner shown when creating a share
|
||||
# https://github.com/owncloud/ocis-reva/issues/302 when sharing a file mime-type field is set to application/octet-stream
|
||||
@@ -549,11 +523,8 @@ apiSharePublicLink1/changingPublicLinkShare.feature:96
|
||||
#
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:63
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:107
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:128
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:151
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:174
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:197
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:221
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:244
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions
|
||||
@@ -708,6 +679,11 @@ apiSharePublicLink2/uploadToPublicLinkShare.feature:103
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:121
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:139
|
||||
#
|
||||
# https://github.com/owncloud/ocis/issues/801 deleting a folder should delete share links to it as well
|
||||
#
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:48
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:49
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/286 Upload-only shares must not overwrite but create a separate file
|
||||
#
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:23
|
||||
@@ -1190,8 +1166,6 @@ apiWebdavOperations/downloadFile.feature:84
|
||||
apiWebdavOperations/downloadFile.feature:85
|
||||
apiWebdavOperations/refuseAccess.feature:21
|
||||
apiWebdavOperations/refuseAccess.feature:22
|
||||
apiWebdavOperations/refuseAccess.feature:33
|
||||
apiWebdavOperations/refuseAccess.feature:34
|
||||
#
|
||||
# https://github.com/owncloud/core/pull/38035 PROPFIND to https://localhost:9200/remote.php/dav/files gets an error 500 response
|
||||
#
|
||||
@@ -1535,15 +1509,6 @@ apiWebdavPreviews/previews.feature:166
|
||||
apiWebdavPreviews/previews.feature:178
|
||||
apiWebdavPreviews/previews.feature:179
|
||||
#
|
||||
# https://github.com/owncloud/ocis-ocs/issues/35 group support is not yet implemented
|
||||
#
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:93
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:94
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:114
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:115
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:116
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:117
|
||||
#
|
||||
# https://github.com/owncloud/product/issues/241 deleting an item updates etags of grandparent but not on parent
|
||||
#
|
||||
apiWebdavEtagPropagation1/deleteFileFolder.feature:25
|
||||
|
||||
@@ -172,24 +172,12 @@ apiProvisioning-v2/enableUser.feature:32
|
||||
apiProvisioning-v2/getUser.feature:34
|
||||
apiProvisioning-v2/getUser.feature:35
|
||||
#
|
||||
# https://github.com/owncloud/ocis-accounts/issues/80
|
||||
# Creating an already existing user works
|
||||
#
|
||||
apiProvisioning-v1/addUser.feature:32
|
||||
apiProvisioning-v1/addUser.feature:39
|
||||
apiProvisioning-v2/addUser.feature:39
|
||||
#
|
||||
# https://github.com/owncloud/product/issues/197
|
||||
# Password can be set to empty
|
||||
#
|
||||
apiProvisioning-v1/addUser.feature:69
|
||||
apiProvisioning-v2/addUser.feature:69
|
||||
#
|
||||
# https://github.com/owncloud/ocis-accounts/issues/128
|
||||
# Username is case sensitive
|
||||
#
|
||||
apiProvisioning-v1/addUser.feature:102
|
||||
#
|
||||
# https://github.com/owncloud/ocis/issues/197
|
||||
# Client token generation not implemented
|
||||
#
|
||||
@@ -201,18 +189,12 @@ apiProvisioning-v2/apiProvisioningUsingAppPassword.feature:67
|
||||
# https://github.com/owncloud/ocis-ocs/issues/28
|
||||
# disable users /cloud/users/disable|enable not available
|
||||
#
|
||||
apiProvisioning-v1/disableUser.feature:11
|
||||
apiProvisioning-v1/disableUser.feature:79
|
||||
apiProvisioning-v1/disableUser.feature:99
|
||||
apiProvisioning-v1/disableUser.feature:107
|
||||
apiProvisioning-v1/disableUser.feature:129
|
||||
apiProvisioning-v1/enableUser.feature:11
|
||||
apiProvisioning-v2/disableUser.feature:11
|
||||
apiProvisioning-v2/disableUser.feature:79
|
||||
apiProvisioning-v2/disableUser.feature:99
|
||||
apiProvisioning-v2/disableUser.feature:108
|
||||
apiProvisioning-v2/disableUser.feature:130
|
||||
apiProvisioning-v2/enableUser.feature:11
|
||||
#
|
||||
# https://github.com/owncloud/ocis-ocs/issues/51
|
||||
# displayname of user can be changed to empty
|
||||
@@ -225,9 +207,6 @@ apiProvisioning-v2/editUser.feature:47
|
||||
#
|
||||
apiProvisioning-v1/editUser.feature:56
|
||||
apiProvisioning-v1/editUser.feature:122
|
||||
apiProvisioning-v1/enableUser.feature:34
|
||||
apiProvisioning-v1/enableUser.feature:56
|
||||
apiProvisioning-v1/enableUser.feature:63
|
||||
apiProvisioning-v2/editUser.feature:56
|
||||
apiProvisioning-v2/editUser.feature:122
|
||||
apiProvisioning-v2/enableUser.feature:34
|
||||
@@ -237,14 +216,10 @@ apiProvisioning-v2/enableUser.feature:64
|
||||
# https://github.com/owncloud/product/issues/248
|
||||
# user can get info of other users/ cloud/users endpoints not authenticated
|
||||
#
|
||||
apiProvisioning-v1/deleteUser.feature:53
|
||||
apiProvisioning-v2/deleteUser.feature:54
|
||||
apiProvisioning-v1/getUser.feature:81
|
||||
apiProvisioning-v1/getUsers.feature:43
|
||||
apiProvisioning-v1/resetUserPassword.feature:56
|
||||
apiProvisioning-v2/getUser.feature:82
|
||||
apiProvisioning-v2/getUsers.feature:44
|
||||
apiProvisioning-v2/resetUserPassword.feature:56
|
||||
#
|
||||
# https://github.com/owncloud/product/issues/250
|
||||
# incorrect ocs(v2) status value when getting info of user that does not exist should be 404, gives 998
|
||||
@@ -314,7 +289,6 @@ apiSharees/sharees.feature:538
|
||||
# https://github.com/owncloud/ocis-reva/issues/34 groups endpoint does not exist
|
||||
#
|
||||
apiShareManagementToShares/acceptShares.feature:22
|
||||
apiShareManagementToShares/acceptShares.feature:52
|
||||
apiShareManagementToShares/acceptShares.feature:71
|
||||
apiShareManagementToShares/acceptShares.feature:156
|
||||
apiShareManagementToShares/acceptShares.feature:157
|
||||
@@ -330,12 +304,9 @@ apiShareManagementToShares/acceptShares.feature:249
|
||||
apiShareManagementToShares/acceptShares.feature:270
|
||||
apiShareManagementToShares/acceptShares.feature:279
|
||||
apiShareManagementToShares/acceptShares.feature:298
|
||||
apiShareManagementToShares/acceptShares.feature:320
|
||||
apiShareManagementToShares/acceptShares.feature:342
|
||||
apiShareManagementToShares/acceptShares.feature:378
|
||||
apiShareManagementToShares/acceptShares.feature:398
|
||||
apiShareManagementToShares/acceptShares.feature:417
|
||||
apiShareManagementToShares/acceptShares.feature:439
|
||||
#
|
||||
# https://github.com/owncloud/product/issues/207 Response is empty when accepting a share
|
||||
#
|
||||
@@ -400,12 +371,8 @@ apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.fe
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:70
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:97
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:98
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:115
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:116
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:135
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:136
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:153
|
||||
apiShareManagementBasicToShares/excludeGroupFromReceivingSharesToSharesFolder.feature:154
|
||||
#
|
||||
# https://github.com/owncloud/ocis/issues/720 shares are mounted into /Shares folder even after the sharer deletes the collaborator
|
||||
# https://github.com/owncloud/ocis/issues/721 deleting share response does not contain `data` field
|
||||
@@ -443,11 +410,6 @@ apiShareOperationsToShares/accessToShare.feature:56
|
||||
apiShareOperationsToShares/gettingShares.feature:24
|
||||
apiShareOperationsToShares/gettingShares.feature:25
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/65 There is no such thing like a "super-user"
|
||||
#
|
||||
apiShareOperationsToShares/gettingShares.feature:38
|
||||
apiShareOperationsToShares/gettingShares.feature:39
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/357 Delete shares from user when user is deleted
|
||||
# https://github.com/owncloud/ocis-reva/issues/301 no displayname_owner shown when creating a share
|
||||
# https://github.com/owncloud/ocis-reva/issues/302 when sharing a file mime-type field is set to application/octet-stream
|
||||
@@ -526,11 +488,8 @@ apiSharePublicLink1/changingPublicLinkShare.feature:96
|
||||
#
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:63
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:107
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:128
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:151
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:174
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:197
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:221
|
||||
apiSharePublicLink1/changingPublicLinkShare.feature:244
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/292 Public link enforce permissions
|
||||
@@ -690,6 +649,11 @@ apiSharePublicLink2/uploadToPublicLinkShare.feature:103
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:121
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:139
|
||||
#
|
||||
# https://github.com/owncloud/ocis/issues/801 deleting a folder should delete share links to it as well
|
||||
#
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:48
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:49
|
||||
#
|
||||
# https://github.com/owncloud/ocis-reva/issues/286 Upload-only shares must not overwrite but create a separate file
|
||||
#
|
||||
apiSharePublicLink2/uploadToPublicLinkShare.feature:23
|
||||
@@ -1175,8 +1139,6 @@ apiWebdavOperations/downloadFile.feature:84
|
||||
apiWebdavOperations/downloadFile.feature:85
|
||||
apiWebdavOperations/refuseAccess.feature:21
|
||||
apiWebdavOperations/refuseAccess.feature:22
|
||||
apiWebdavOperations/refuseAccess.feature:33
|
||||
apiWebdavOperations/refuseAccess.feature:34
|
||||
#
|
||||
# https://github.com/owncloud/core/pull/38035 PROPFIND to https://localhost:9200/remote.php/dav/files gets an error 500 response
|
||||
#
|
||||
@@ -1492,15 +1454,6 @@ apiWebdavPreviews/previews.feature:166
|
||||
apiWebdavPreviews/previews.feature:178
|
||||
apiWebdavPreviews/previews.feature:179
|
||||
#
|
||||
# https://github.com/owncloud/ocis-ocs/issues/35 group support is not yet implemented
|
||||
#
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:93
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:94
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:114
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:115
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:116
|
||||
apiShareCreateSpecialToShares2/createShareWithInvalidPermissions.feature:117
|
||||
#
|
||||
# https://github.com/owncloud/ocis/issues/762 path and other information are not shown if a share does not have "read" permission
|
||||
#
|
||||
apiShareOperationsToShares/uploadToShare.feature:64
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
Feature: auth
|
||||
|
||||
# these endpoints are handled by the reva ocs implementation
|
||||
Scenario: send DELETE requests to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "DELETE" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
@@ -20,39 +21,15 @@ Feature: auth
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
# these endpoints are handled by the ocis ocs implementation
|
||||
Scenario: send DELETE requests to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "DELETE" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users/%username% |
|
||||
| /ocs/v1.php/cloud/users/%username%/subadmins |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
|
||||
Scenario: send DELETE requests to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "DELETE" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username% |
|
||||
Then the HTTP status code of responses on all endpoints should be "404"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
|
||||
Scenario: send DELETE requests to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "DELETE" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users/%username%/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "996"
|
||||
|
||||
Scenario: send DELETE requests to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "DELETE" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username%/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "500"
|
||||
And the OCS status code of responses on all endpoints should be "996"
|
||||
|
||||
Scenario: send DELETE requests to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "DELETE" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username% |
|
||||
| /ocs/v1.php/cloud/users/%username%/subadmins |
|
||||
| /ocs/v2.php/cloud/users/%username%/subadmins |
|
||||
Then the HTTP status code of responses on all endpoints should be "404"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
| /ocs/v1.php/cloud/users/%username%/groups |
|
||||
| /ocs/v2.php/cloud/users/%username%/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
@@ -20,8 +20,6 @@ Feature: auth
|
||||
| /ocs/v2.php/apps/files_sharing/api/v1/shares |
|
||||
| /ocs/v1.php/cloud/apps |
|
||||
| /ocs/v2.php/cloud/apps |
|
||||
| /ocs/v1.php/cloud/groups |
|
||||
| /ocs/v2.php/cloud/groups |
|
||||
| /ocs/v1.php/config |
|
||||
| /ocs/v2.php/config |
|
||||
| /ocs/v1.php/privatedata/getattribute |
|
||||
@@ -33,15 +31,13 @@ Feature: auth
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
Scenario: using OCS anonymously
|
||||
When a user requests these endpoints with "GET" and no authentication
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "100"
|
||||
When a user requests these endpoints with "GET" and no authentication
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "200"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
| /ocs/v1.php/cloud/groups |
|
||||
| /ocs/v2.php/cloud/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "997"
|
||||
|
||||
|
||||
@issue-ocis-reva-11
|
||||
@@ -59,7 +55,6 @@ Feature: auth
|
||||
| /ocs/v1.php/apps/files_sharing/api/v1/remote_shares |
|
||||
| /ocs/v1.php/apps/files_sharing/api/v1/remote_shares/pending |
|
||||
| /ocs/v1.php/privatedata/getattribute |
|
||||
| /ocs/v1.php/cloud/groups |
|
||||
| /ocs/v1.php/cloud/apps |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
@@ -77,20 +72,17 @@ Feature: auth
|
||||
# | /ocs/v2.php/apps/files_sharing/api/v1/shares | 100 | 200 |
|
||||
|
||||
| /ocs/v2.php/cloud/apps |
|
||||
| /ocs/v2.php/cloud/groups |
|
||||
| /ocs/v2.php/privatedata/getattribute |
|
||||
Then the HTTP status code of responses on all endpoints should be "404"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
When the user "Alice" requests these endpoints with "GET" with basic auth
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "100"
|
||||
When the user "Alice" requests these endpoints with "GET" with basic auth
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "200"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
| /ocs/v1.php/cloud/groups |
|
||||
| /ocs/v2.php/cloud/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "997"
|
||||
When the user "Alice" requests these endpoints with "GET" with basic auth
|
||||
| endpoint |
|
||||
| /ocs/v2.php/config |
|
||||
@@ -133,13 +125,9 @@ Feature: auth
|
||||
When user "Alice" requests these endpoints with "GET" using password "invalid"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "100"
|
||||
When user "Alice" requests these endpoints with "GET" using password "invalid"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "200"
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
@skipOnOcV10
|
||||
@issue-ocis-reva-29
|
||||
@@ -183,10 +171,6 @@ Feature: auth
|
||||
When user "brian" requests these endpoints with "GET" using password "invalid"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "100"
|
||||
When user "brian" requests these endpoints with "GET" using password "invalid"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "200"
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
@@ -28,43 +28,16 @@ Feature: auth
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
Scenario: send POST requests to OCS endpoints as normal user with wrong password
|
||||
When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "400"
|
||||
When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
Then the HTTP status code of responses on all endpoints should be "400"
|
||||
And the OCS status code of responses on all endpoints should be "400"
|
||||
|
||||
@issue-ocis-reva-30
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
Scenario: send POST requests to OCS endpoints as normal user with wrong password
|
||||
When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users/%username%/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "400"
|
||||
When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username%/groups |
|
||||
Then the HTTP status code of responses on all endpoints should be "400"
|
||||
And the OCS status code of responses on all endpoints should be "400"
|
||||
|
||||
@issue-ocis-reva-30
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
Scenario: send POST requests to OCS endpoints as normal user with wrong password
|
||||
When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users |
|
||||
| /ocs/v2.php/cloud/users |
|
||||
| /ocs/v1.php/cloud/users/%username%/groups |
|
||||
| /ocs/v2.php/cloud/users/%username%/groups |
|
||||
| /ocs/v1.php/cloud/users/%username%/subadmins |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
When user "Alice" requests these endpoints with "POST" including body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username%/subadmins |
|
||||
Then the HTTP status code of responses on all endpoints should be "404"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
@@ -9,24 +9,11 @@ Feature: auth
|
||||
| endpoint |
|
||||
| /ocs/v1.php/apps/files_sharing/api/v1/shares/123 |
|
||||
| /ocs/v2.php/apps/files_sharing/api/v1/shares/123 |
|
||||
| /ocs/v1.php/cloud/users/%username% |
|
||||
| /ocs/v2.php/cloud/users/%username% |
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
@issue-ocis-reva-30
|
||||
@issue-ocis-ocs-26
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
Scenario: send PUT request to OCS endpoints as admin with wrong password
|
||||
When the administrator requests these endpoints with "PUT" with body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users/%username% |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
When the administrator requests these endpoints with "PUT" with body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username% |
|
||||
Then the HTTP status code of responses on all endpoints should be "404"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
|
||||
@issue-ocis-reva-30
|
||||
@issue-ocis-ocs-28
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
@@ -34,13 +21,9 @@ Feature: auth
|
||||
When the administrator requests these endpoints with "PUT" with body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v1.php/cloud/users/%username%/disable |
|
||||
| /ocs/v1.php/cloud/users/%username%/enable |
|
||||
Then the HTTP status code of responses on all endpoints should be "200"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
When the administrator requests these endpoints with "PUT" with body "doesnotmatter" using password "invalid" about user "Alice"
|
||||
| endpoint |
|
||||
| /ocs/v2.php/cloud/users/%username%/disable |
|
||||
| /ocs/v1.php/cloud/users/%username%/enable |
|
||||
| /ocs/v2.php/cloud/users/%username%/enable |
|
||||
Then the HTTP status code of responses on all endpoints should be "404"
|
||||
And the OCS status code of responses on all endpoints should be "998"
|
||||
Then the HTTP status code of responses on all endpoints should be "401"
|
||||
And the OCS status code of responses on all endpoints should be "notset"
|
||||
|
||||
|
||||
+16
@@ -15,3 +15,19 @@ Feature: upload to a public link share
|
||||
When user "Alice" deletes file "/FOLDER" using the WebDAV API
|
||||
And the public uploads file "does-not-matter.txt" with content "does not matter" using the new public WebDAV API
|
||||
Then the HTTP status code should be "500"
|
||||
|
||||
@issue-ocis-801
|
||||
# after fixing all issues delete this Scenario and use the one from oC10 core
|
||||
Scenario Outline: Uploading file to a public upload-only share using old public API that was deleted does not work
|
||||
Given using <dav-path> DAV path
|
||||
And user "Alice" has created a public link share with settings
|
||||
| path | FOLDER |
|
||||
| permissions | create |
|
||||
When user "Alice" deletes file "/FOLDER" using the WebDAV API
|
||||
Then uploading a file should not work using the old public WebDAV API
|
||||
And the HTTP status code should be "401"
|
||||
|
||||
Examples:
|
||||
| dav-path |
|
||||
| old |
|
||||
| new |
|
||||
@@ -7,6 +7,7 @@ require (
|
||||
contrib.go.opencensus.io/exporter/ocagent v0.7.0
|
||||
contrib.go.opencensus.io/exporter/zipkin v0.1.1
|
||||
github.com/UnnoTed/fileb0x v1.1.4
|
||||
github.com/cs3org/go-cs3apis v0.0.0-20201007120910-416ed6cf8b00
|
||||
github.com/cs3org/reva v1.3.1-0.20201023144216-cdb3d6688da5
|
||||
github.com/go-chi/chi v4.1.2+incompatible
|
||||
github.com/go-chi/render v1.0.1
|
||||
@@ -26,6 +27,7 @@ require (
|
||||
github.com/stretchr/testify v1.6.1
|
||||
go.opencensus.io v0.22.5
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
google.golang.org/genproto v0.0.0-20200624020401-64a14ca9d1ad
|
||||
google.golang.org/protobuf v1.25.0
|
||||
)
|
||||
|
||||
|
||||
+39
-1061
File diff suppressed because it is too large
Load Diff
@@ -1,40 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/pkg/token/manager/jwt"
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
)
|
||||
|
||||
// AccessToken middleware is used to set the user from an x-access-token to the context
|
||||
func AccessToken(opts ...Option) func(next http.Handler) http.Handler {
|
||||
opt := newOptions(opts...)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
// TODO: handle error
|
||||
tokenManager, err := jwt.New(map[string]interface{}{
|
||||
"secret": opt.TokenManagerConfig.JWTSecret,
|
||||
"expires": int64(60),
|
||||
})
|
||||
if err != nil {
|
||||
opt.Logger.Fatal().Err(err).Msgf("Could not initialize token-manager")
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
token := r.Header.Get("x-access-token")
|
||||
if token != "" {
|
||||
u, err := tokenManager.DismantleToken(r.Context(), token)
|
||||
if err != nil {
|
||||
opt.Logger.Error().Err(err).Msg("could not dismantle token")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// store user in context for request
|
||||
r = r.WithContext(user.ContextSetUser(r.Context(), u))
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/owncloud/ocis/ocs/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/ocis-pkg/roles"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
@@ -12,8 +12,8 @@ type Option func(o *Options)
|
||||
type Options struct {
|
||||
// Logger to use for logging, must be set
|
||||
Logger log.Logger
|
||||
// TokenManagerConfig for communicating with the reva token manager
|
||||
TokenManagerConfig config.TokenManager
|
||||
// RoleManager for looking up permissions
|
||||
RoleManager *roles.Manager
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -34,9 +34,9 @@ func Logger(l log.Logger) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// TokenManagerConfig provides a function to set the token manger config option.
|
||||
func TokenManagerConfig(cfg config.TokenManager) Option {
|
||||
// RoleManager provides a function to set the RoleManager option.
|
||||
func RoleManager(val *roles.Manager) Option {
|
||||
return func(o *Options) {
|
||||
o.TokenManagerConfig = cfg
|
||||
o.RoleManager = val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
accounts "github.com/owncloud/ocis/accounts/pkg/service/v0"
|
||||
"github.com/owncloud/ocis/ocis-pkg/roles"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/response"
|
||||
)
|
||||
|
||||
// RequireAdmin middleware is used to require the user in context to be an admin / have account management permissions
|
||||
func RequireAdmin(opts ...Option) func(next http.Handler) http.Handler {
|
||||
opt := newOptions(opts...)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// get roles from context
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context())
|
||||
if !ok {
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
// check if permission is present in roles of the authenticated account
|
||||
if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, accounts.AccountManagementPermissionID) != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
accounts "github.com/owncloud/ocis/accounts/pkg/service/v0"
|
||||
"github.com/owncloud/ocis/ocis-pkg/roles"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/response"
|
||||
)
|
||||
|
||||
// RequireSelfOrAdmin middleware is used to require the requesting user to be an admin or the requested user himself
|
||||
func RequireSelfOrAdmin(opts ...Option) func(next http.Handler) http.Handler {
|
||||
opt := newOptions(opts...)
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
|
||||
return
|
||||
}
|
||||
if u.Id == nil || u.Id.OpaqueId == "" {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id"))
|
||||
return
|
||||
}
|
||||
// get roles from context
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(r.Context())
|
||||
if !ok {
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
// check if account management permission is present in roles of the authenticated account
|
||||
if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, accounts.AccountManagementPermissionID) != nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// check if self management permission is present in roles of the authenticated account
|
||||
if opt.RoleManager.FindPermissionByID(r.Context(), roleIDs, accounts.SelfManagementPermissionID) != nil {
|
||||
userid := chi.URLParam(r, "userid")
|
||||
if userid == "" || userid == u.Id.OpaqueId || userid == u.Username {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/response"
|
||||
)
|
||||
|
||||
// RequireUser middleware is used to require a user in context
|
||||
func RequireUser() func(next http.Handler) http.Handler {
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnauthorized.StatusCode, "Unauthorized"))
|
||||
return
|
||||
}
|
||||
if u.Id == nil || u.Id.OpaqueId == "" {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id"))
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
+409
-342
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,9 @@ var MetaFailure = Meta{Status: "", StatusCode: 101, Message: "Failure"}
|
||||
// MetaInvalidInput is an error response with code 102
|
||||
var MetaInvalidInput = Meta{Status: "", StatusCode: 102, Message: "Invalid Input"}
|
||||
|
||||
// MetaForbidden is an error response with code 104
|
||||
var MetaForbidden = Meta{Status: "", StatusCode: 104, Message: "Forbidden"}
|
||||
|
||||
// MetaBadRequest is used for unknown errors
|
||||
var MetaBadRequest = Meta{Status: "error", StatusCode: 400, Message: "Bad Request"}
|
||||
|
||||
|
||||
+179
-19
@@ -1,9 +1,13 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
@@ -41,10 +45,28 @@ func (o Ocs) ListUserGroups(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
groups := []string{}
|
||||
for i := range account.MemberOf {
|
||||
groups = append(groups, account.MemberOf[i].Id)
|
||||
if account.MemberOf[i].OnPremisesSamAccountName == "" {
|
||||
o.logger.Warn().Str("groupid", account.MemberOf[i].Id).Msg("group on_premises_sam_account_name is empty, trying to lookup by id")
|
||||
// we can try to look up the name
|
||||
group, err := o.getGroupsService().GetGroup(r.Context(), &accounts.GetGroupRequest{
|
||||
Id: account.MemberOf[i].Id,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
o.logger.Error().Err(err).Str("groupid", account.MemberOf[i].Id).Msg("could not get group")
|
||||
continue
|
||||
}
|
||||
if group.OnPremisesSamAccountName == "" {
|
||||
o.logger.Error().Err(err).Str("groupid", account.MemberOf[i].Id).Msg("group on_premises_sam_account_name is empty")
|
||||
continue
|
||||
}
|
||||
groups = append(groups, group.OnPremisesSamAccountName)
|
||||
} else {
|
||||
groups = append(groups, account.MemberOf[i].OnPremisesSamAccountName)
|
||||
}
|
||||
}
|
||||
|
||||
o.logger.Error().Err(err).Int("count", len(groups)).Str("userid", userid).Msg("listing groups for user")
|
||||
o.logger.Error().Err(err).Int("count", len(groups)).Str("userid", account.Id).Msg("listing groups for user")
|
||||
render.Render(w, r, response.DataRender(&data.Groups{Groups: groups}))
|
||||
}
|
||||
|
||||
@@ -69,9 +91,21 @@ func (o Ocs) AddToGroup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// ocs only knows about names so we have to look up the internal id
|
||||
group, err := o.fetchGroupByName(r.Context(), groupid)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested group could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = o.getGroupsService().AddMember(r.Context(), &accounts.AddMemberRequest{
|
||||
AccountId: account.Id,
|
||||
GroupId: groupid,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -81,21 +115,46 @@ func (o Ocs) AddToGroup(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", userid).Str("groupid", groupid).Msg("could not add user to group")
|
||||
o.logger.Error().Err(err).Str("userid", account.Id).Str("groupid", group.Id).Msg("could not add user to group")
|
||||
return
|
||||
}
|
||||
|
||||
o.logger.Debug().Str("userid", userid).Str("groupid", groupid).Msg("added user to group")
|
||||
o.logger.Debug().Str("userid", account.Id).Str("groupid", group.Id).Msg("added user to group")
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
// RemoveFromGroup removes a user from a group
|
||||
func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) {
|
||||
userid := chi.URLParam(r, "userid")
|
||||
groupid := r.URL.Query().Get("groupid")
|
||||
|
||||
var err error
|
||||
|
||||
// Really? a DELETE with form encoded body?!?
|
||||
// but it is not encoded as mime, so we cannot just call r.ParseForm()
|
||||
// read it manually
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, err.Error()))
|
||||
return
|
||||
}
|
||||
if err = r.Body.Close(); err != nil {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
values, err := url.ParseQuery(string(body))
|
||||
if err != nil {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
groupid := values.Get("groupid")
|
||||
if groupid == "" {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
var account *accounts.Account
|
||||
var err error
|
||||
|
||||
if isValidUUID(userid) {
|
||||
account, _ = o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{
|
||||
@@ -116,9 +175,21 @@ func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// ocs only knows about names so we have to look up the internal id
|
||||
group, err := o.fetchGroupByName(r.Context(), groupid)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested group could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = o.getGroupsService().RemoveMember(r.Context(), &accounts.RemoveMemberRequest{
|
||||
AccountId: account.Id,
|
||||
GroupId: groupid,
|
||||
GroupId: group.Id,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -128,11 +199,11 @@ func (o Ocs) RemoveFromGroup(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", userid).Str("groupid", groupid).Msg("could not remove user from group")
|
||||
o.logger.Error().Err(err).Str("userid", account.Id).Str("groupid", group.Id).Msg("could not remove user from group")
|
||||
return
|
||||
}
|
||||
|
||||
o.logger.Debug().Str("userid", userid).Str("groupid", groupid).Msg("removed user from group")
|
||||
o.logger.Debug().Str("userid", account.Id).Str("groupid", group.Id).Msg("removed user from group")
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
@@ -156,7 +227,7 @@ func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
groups := []string{}
|
||||
for i := range res.Groups {
|
||||
groups = append(groups, res.Groups[i].Id)
|
||||
groups = append(groups, res.Groups[i].OnPremisesSamAccountName)
|
||||
}
|
||||
|
||||
render.Render(w, r, response.DataRender(&data.Groups{Groups: groups}))
|
||||
@@ -164,15 +235,78 @@ func (o Ocs) ListGroups(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// AddGroup adds a group
|
||||
func (o Ocs) AddGroup(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "not implemented"))
|
||||
groupid := r.PostFormValue("groupid")
|
||||
displayname := r.PostFormValue("displayname")
|
||||
gid := r.PostFormValue("gidnumber")
|
||||
|
||||
var gidNumber int64
|
||||
var err error
|
||||
|
||||
if gid != "" {
|
||||
gidNumber, err = strconv.ParseInt(gid, 10, 64)
|
||||
if err != nil {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "Cannot use the gidnumber provided"))
|
||||
o.logger.Error().Err(err).Str("gid", gid).Str("groupid", groupid).Msg("Cannot use the gidnumber provided")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if displayname == "" {
|
||||
displayname = groupid
|
||||
}
|
||||
|
||||
newGroup := &accounts.Group{
|
||||
Id: groupid,
|
||||
DisplayName: displayname,
|
||||
OnPremisesSamAccountName: groupid,
|
||||
GidNumber: gidNumber,
|
||||
}
|
||||
group, err := o.getGroupsService().CreateGroup(r.Context(), &accounts.CreateGroupRequest{
|
||||
Group: newGroup,
|
||||
})
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
switch merr.Code {
|
||||
case http.StatusBadRequest:
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
|
||||
case http.StatusConflict:
|
||||
if response.APIVersion(r.Context()) == "2" {
|
||||
// it seems the application framework sets the ocs status code to the httpstatus code, which affects the provisioning api
|
||||
// see https://github.com/owncloud/core/blob/b9ff4c93e051c94adfb301545098ae627e52ef76/lib/public/AppFramework/OCSController.php#L142-L150
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaInvalidInput.StatusCode, merr.Detail))
|
||||
}
|
||||
default:
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("groupid", groupid).Msg("could not add group")
|
||||
// TODO check error if group already existed
|
||||
return
|
||||
}
|
||||
o.logger.Debug().Interface("group", group).Msg("added group")
|
||||
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
// DeleteGroup deletes a group
|
||||
func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) {
|
||||
groupid := chi.URLParam(r, "groupid")
|
||||
|
||||
_, err := o.getGroupsService().DeleteGroup(r.Context(), &accounts.DeleteGroupRequest{
|
||||
Id: groupid,
|
||||
// ocs only knows about names so we have to look up the internal id
|
||||
group, err := o.fetchGroupByName(r.Context(), groupid)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested group could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = o.getGroupsService().DeleteGroup(r.Context(), &accounts.DeleteGroupRequest{
|
||||
Id: group.Id,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -182,11 +316,11 @@ func (o Ocs) DeleteGroup(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("groupid", groupid).Msg("could not remove group")
|
||||
o.logger.Error().Err(err).Str("groupid", group.Id).Msg("could not remove group")
|
||||
return
|
||||
}
|
||||
|
||||
o.logger.Debug().Str("groupid", groupid).Msg("removed group")
|
||||
o.logger.Debug().Str("groupid", group.Id).Msg("removed group")
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
@@ -195,7 +329,19 @@ func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
groupid := chi.URLParam(r, "groupid")
|
||||
|
||||
res, err := o.getGroupsService().ListMembers(r.Context(), &accounts.ListMembersRequest{Id: groupid})
|
||||
// ocs only knows about names so we have to look up the internal id
|
||||
group, err := o.fetchGroupByName(r.Context(), groupid)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested group could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
res, err := o.getGroupsService().ListMembers(r.Context(), &accounts.ListMembersRequest{Id: group.Id})
|
||||
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
@@ -204,13 +350,13 @@ func (o Ocs) GetGroupMembers(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("groupid", groupid).Msg("could not get list of members")
|
||||
o.logger.Error().Err(err).Str("groupid", group.Id).Msg("could not get list of members")
|
||||
return
|
||||
}
|
||||
|
||||
members := []string{}
|
||||
for i := range res.Members {
|
||||
members = append(members, res.Members[i].Id)
|
||||
members = append(members, res.Members[i].OnPremisesSamAccountName)
|
||||
}
|
||||
|
||||
o.logger.Error().Err(err).Int("count", len(members)).Str("groupid", groupid).Msg("listing group members")
|
||||
@@ -221,3 +367,17 @@ func isValidUUID(uuid string) bool {
|
||||
r := regexp.MustCompile("^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[8|9|aA|bB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}$")
|
||||
return r.MatchString(uuid)
|
||||
}
|
||||
|
||||
func (o Ocs) fetchGroupByName(ctx context.Context, name string) (*accounts.Group, error) {
|
||||
var res *accounts.ListGroupsResponse
|
||||
res, err := o.getGroupsService().ListGroups(ctx, &accounts.ListGroupsRequest{
|
||||
Query: fmt.Sprintf("on_premises_sam_account_name eq '%v'", escapeValue(name)),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if res != nil && len(res.Groups) == 1 {
|
||||
return res.Groups[0], nil
|
||||
}
|
||||
return nil, merrors.NotFound("", "The requested group could not be found")
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package svc
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/owncloud/ocis/ocs/pkg/config"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/ocis-pkg/roles"
|
||||
"github.com/owncloud/ocis/ocs/pkg/config"
|
||||
settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
@@ -12,9 +14,11 @@ type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
Logger log.Logger
|
||||
Config *config.Config
|
||||
Middleware []func(http.Handler) http.Handler
|
||||
RoleService settings.RoleService
|
||||
RoleManager *roles.Manager
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -48,3 +52,17 @@ func Middleware(val ...func(http.Handler) http.Handler) Option {
|
||||
o.Middleware = val
|
||||
}
|
||||
}
|
||||
|
||||
// RoleService provides a function to set the RoleService option.
|
||||
func RoleService(val settings.RoleService) Option {
|
||||
return func(o *Options) {
|
||||
o.RoleService = val
|
||||
}
|
||||
}
|
||||
|
||||
// RoleManager provides a function to set the RoleManager option.
|
||||
func RoleManager(val *roles.Manager) Option {
|
||||
return func(o *Options) {
|
||||
o.RoleManager = val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +32,17 @@ func APIVersion(ctx context.Context) string {
|
||||
|
||||
// OcsV1StatusCodes returns the http status codes for the OCS API v1.
|
||||
func OcsV1StatusCodes(meta data.Meta) int {
|
||||
if meta.StatusCode == data.MetaUnauthorized.StatusCode {
|
||||
return http.StatusUnauthorized
|
||||
}
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
// OcsV2StatusCodes maps the OCS codes to http status codes for the ocs API v2.
|
||||
// see https://github.com/owncloud/core/blob/c08baf580927ecb8ec179028dda255fdd85b4568/lib/private/legacy/api.php#L528
|
||||
// also HTTP status codes for apps are the same as OCS codes
|
||||
// see https://github.com/owncloud/core/blob/b9ff4c93e051c94adfb301545098ae627e52ef76/lib/public/AppFramework/OCSController.php#L142-L150
|
||||
// I think this is a bug in the ocs v2 api, but since we are going to mimic bugs in ocis ... here goes
|
||||
func OcsV2StatusCodes(meta data.Meta) int {
|
||||
sc := meta.StatusCode
|
||||
switch sc {
|
||||
@@ -47,7 +54,8 @@ func OcsV2StatusCodes(meta data.Meta) int {
|
||||
return http.StatusInternalServerError
|
||||
case data.MetaUnauthorized.StatusCode:
|
||||
return http.StatusUnauthorized
|
||||
case 100:
|
||||
case data.MetaOK.StatusCode:
|
||||
// TODO mustn't data.Meta be a pointer so this assignment has an effect
|
||||
meta.StatusCode = http.StatusOK
|
||||
return http.StatusOK
|
||||
}
|
||||
@@ -61,7 +69,7 @@ func OcsV2StatusCodes(meta data.Meta) int {
|
||||
return http.StatusBadRequest
|
||||
}
|
||||
|
||||
// TODO change this status code?
|
||||
// TODO change this status code? yes, align with oc10 core mapStatusCodes
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,24 @@ package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/micro/go-micro/v2/client/grpc"
|
||||
|
||||
mclient "github.com/micro/go-micro/v2/client"
|
||||
accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0"
|
||||
"github.com/owncloud/ocis/ocis-pkg/account"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
opkgm "github.com/owncloud/ocis/ocis-pkg/middleware"
|
||||
"github.com/owncloud/ocis/ocis-pkg/roles"
|
||||
"github.com/owncloud/ocis/ocs/pkg/config"
|
||||
ocsm "github.com/owncloud/ocis/ocs/pkg/middleware"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis/ocs/pkg/service/v0/response"
|
||||
"github.com/owncloud/ocis/ocis-pkg/log"
|
||||
settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
|
||||
)
|
||||
|
||||
var defaultClient = grpc.NewClient()
|
||||
@@ -31,19 +37,47 @@ func NewService(opts ...Option) Service {
|
||||
m := chi.NewMux()
|
||||
m.Use(options.Middleware...)
|
||||
|
||||
svc := Ocs{
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
logger: options.Logger,
|
||||
roleService := options.RoleService
|
||||
if roleService == nil {
|
||||
// https://github.com/owncloud/ocis-proxy/issues/38
|
||||
// TODO this won't work with a registry other than mdns. Look into Micro's client initialization.
|
||||
roleService = settings.NewRoleService("com.owncloud.api.settings", mclient.DefaultClient)
|
||||
}
|
||||
roleManager := options.RoleManager
|
||||
if roleManager == nil {
|
||||
m := roles.NewManager(
|
||||
roles.CacheSize(1024),
|
||||
roles.CacheTTL(time.Hour*24*7),
|
||||
roles.Logger(options.Logger),
|
||||
roles.RoleService(roleService),
|
||||
)
|
||||
roleManager = &m
|
||||
}
|
||||
|
||||
svc := Ocs{
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
RoleManager: roleManager,
|
||||
logger: options.Logger,
|
||||
}
|
||||
|
||||
requireUser := ocsm.RequireUser()
|
||||
|
||||
requireAdmin := ocsm.RequireAdmin(
|
||||
ocsm.RoleManager(roleManager),
|
||||
)
|
||||
|
||||
requireSelfOrAdmin := ocsm.RequireSelfOrAdmin(
|
||||
ocsm.RoleManager(roleManager),
|
||||
ocsm.Logger(options.Logger),
|
||||
)
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
r.NotFound(svc.NotFound)
|
||||
r.Use(middleware.StripSlashes)
|
||||
r.Use(ocsm.AccessToken(
|
||||
ocsm.Logger(options.Logger),
|
||||
ocsm.TokenManagerConfig(options.Config.TokenManager),
|
||||
))
|
||||
r.Use(opkgm.ExtractAccountUUID(
|
||||
account.Logger(options.Logger),
|
||||
account.JWTSecret(options.Config.TokenManager.JWTSecret)),
|
||||
)
|
||||
r.Use(ocsm.OCSFormatCtx) // updates request Accept header according to format=(json|xml) query parameter
|
||||
r.Route("/v{version:(1|2)}.php", func(r chi.Router) {
|
||||
r.Use(response.VersionCtx) // stores version in context
|
||||
@@ -51,32 +85,48 @@ func NewService(opts ...Option) Service {
|
||||
r.Route("/apps/notifications/api/v1", func(r chi.Router) {})
|
||||
r.Route("/cloud", func(r chi.Router) {
|
||||
r.Route("/capabilities", func(r chi.Router) {})
|
||||
// TODO /apps
|
||||
r.Route("/user", func(r chi.Router) {
|
||||
r.Get("/", svc.GetUser)
|
||||
r.With(requireSelfOrAdmin).Get("/", svc.GetSelf)
|
||||
r.Get("/signing-key", svc.GetSigningKey)
|
||||
})
|
||||
|
||||
// for /users endpoints see https://github.com/owncloud/core/blob/master/apps/provisioning_api/appinfo/routes.php#L44-L56
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", svc.ListUsers)
|
||||
r.Post("/", svc.AddUser)
|
||||
r.Get("/{userid}", svc.GetUser)
|
||||
r.Put("/{userid}", svc.EditUser)
|
||||
r.Delete("/{userid}", svc.DeleteUser)
|
||||
r.With(requireAdmin).Get("/", svc.ListUsers)
|
||||
r.With(requireAdmin).Post("/", svc.AddUser)
|
||||
r.Route("/{userid}", func(r chi.Router) {
|
||||
r.With(requireUser).Get("/", svc.GetUser)
|
||||
r.With(requireSelfOrAdmin).Put("/", svc.EditUser)
|
||||
r.With(requireAdmin).Delete("/", svc.DeleteUser)
|
||||
r.With(requireAdmin).Put("/enable", svc.EnableUser)
|
||||
r.With(requireAdmin).Put("/disable", svc.DisableUser)
|
||||
})
|
||||
|
||||
r.Route("/{userid}/groups", func(r chi.Router) {
|
||||
r.Get("/", svc.ListUserGroups)
|
||||
r.Post("/", svc.AddToGroup)
|
||||
r.Delete("/", svc.RemoveFromGroup)
|
||||
r.With(requireSelfOrAdmin).Get("/", svc.ListUserGroups)
|
||||
r.With(requireAdmin).Post("/", svc.AddToGroup)
|
||||
r.With(requireAdmin).Delete("/", svc.RemoveFromGroup)
|
||||
})
|
||||
|
||||
r.Route("/{userid}/subadmins", func(r chi.Router) {
|
||||
r.With(requireAdmin).Post("/", svc.NotImplementedStub)
|
||||
r.With(requireSelfOrAdmin).Get("/", svc.NotImplementedStub)
|
||||
r.With(requireAdmin).Delete("/", svc.NotImplementedStub)
|
||||
})
|
||||
})
|
||||
|
||||
// for /groups endpoints see https://github.com/owncloud/core/blob/master/apps/provisioning_api/appinfo/routes.php#L65-L69
|
||||
r.Route("/groups", func(r chi.Router) {
|
||||
r.Get("/", svc.ListGroups)
|
||||
r.Post("/", svc.AddGroup)
|
||||
r.Delete("/{groupid}", svc.DeleteGroup)
|
||||
r.Get("/{groupid}", svc.GetGroupMembers)
|
||||
r.With(requireAdmin).Get("/", svc.ListGroups)
|
||||
r.With(requireAdmin).Post("/", svc.AddGroup)
|
||||
r.With(requireSelfOrAdmin).Get("/{groupid}", svc.GetGroupMembers)
|
||||
r.With(requireAdmin).Delete("/{groupid}", svc.DeleteGroup)
|
||||
r.With(requireAdmin).Get("/{groupid}/subadmins", svc.NotImplementedStub)
|
||||
})
|
||||
})
|
||||
r.Route("/config", func(r chi.Router) {
|
||||
r.Get("/", svc.GetConfig)
|
||||
r.With(requireUser).Get("/", svc.GetConfig)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -86,9 +136,11 @@ func NewService(opts ...Option) Service {
|
||||
|
||||
// Ocs defines implements the business logic for Service.
|
||||
type Ocs struct {
|
||||
config *config.Config
|
||||
logger log.Logger
|
||||
mux *chi.Mux
|
||||
config *config.Config
|
||||
logger log.Logger
|
||||
RoleService settings.RoleService
|
||||
RoleManager *roles.Manager
|
||||
mux *chi.Mux
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
@@ -108,3 +160,8 @@ func (o Ocs) getAccountService() accounts.AccountsService {
|
||||
func (o Ocs) getGroupsService() accounts.GroupsService {
|
||||
return accounts.NewGroupsService("com.owncloud.api.accounts", defaultClient)
|
||||
}
|
||||
|
||||
// NotImplementedStub returns a not implemented error
|
||||
func (o Ocs) NotImplementedStub(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "Not implemented"))
|
||||
}
|
||||
|
||||
+143
-20
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
"google.golang.org/genproto/protobuf/field_mask"
|
||||
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
||||
|
||||
"github.com/micro/go-micro/v2/client/grpc"
|
||||
@@ -22,22 +23,58 @@ import (
|
||||
storepb "github.com/owncloud/ocis/store/pkg/proto/v0"
|
||||
)
|
||||
|
||||
// GetUser returns the currently logged in user
|
||||
// GetSelf returns the currently logged in user
|
||||
func (o Ocs) GetSelf(w http.ResponseWriter, r *http.Request) {
|
||||
var account *accounts.Account
|
||||
var err error
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok || u.Id == nil || u.Id.OpaqueId == "" {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "user is missing an id"))
|
||||
return
|
||||
}
|
||||
|
||||
account, err = o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{
|
||||
Id: u.Id.OpaqueId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
// if the user was authenticated why was he not found?!? log error?
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(merr).Interface("user", u).Msg("could not get account for user")
|
||||
return
|
||||
}
|
||||
|
||||
// remove password from log if it is set
|
||||
if account.PasswordProfile != nil {
|
||||
account.PasswordProfile.Password = ""
|
||||
}
|
||||
o.logger.Debug().Interface("account", account).Msg("got user")
|
||||
|
||||
d := &data.User{
|
||||
UserID: account.Id,
|
||||
DisplayName: account.DisplayName,
|
||||
LegacyDisplayName: account.DisplayName,
|
||||
Email: account.Mail,
|
||||
UIDNumber: account.UidNumber,
|
||||
GIDNumber: account.GidNumber,
|
||||
// TODO hide enabled flag or it might get rendered as false
|
||||
}
|
||||
render.Render(w, r, response.DataRender(d))
|
||||
}
|
||||
|
||||
// GetUser returns the user with the given userid
|
||||
func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO this endpoint needs authentication using the roles and permissions
|
||||
userid := chi.URLParam(r, "userid")
|
||||
var account *accounts.Account
|
||||
var err error
|
||||
|
||||
if userid == "" {
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok || u.Id == nil || u.Id.OpaqueId == "" {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context"))
|
||||
return
|
||||
}
|
||||
account, err = o.getAccountService().GetAccount(r.Context(), &accounts.GetAccountRequest{
|
||||
Id: u.Id.OpaqueId,
|
||||
})
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context"))
|
||||
} else {
|
||||
account, err = o.fetchAccountByUsername(r.Context(), userid)
|
||||
}
|
||||
@@ -48,7 +85,7 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", userid).Msg("could not get user")
|
||||
o.logger.Error().Err(merr).Str("userid", userid).Msg("could not get account for user")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -67,14 +104,13 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
d := &data.User{
|
||||
UserID: account.PreferredName,
|
||||
UserID: account.Id,
|
||||
DisplayName: account.DisplayName,
|
||||
LegacyDisplayName: account.DisplayName,
|
||||
Email: account.Mail,
|
||||
UIDNumber: account.UidNumber,
|
||||
GIDNumber: account.GidNumber,
|
||||
Enabled: enabled,
|
||||
// FIXME onlyfor users/{userid} endpoint (not /user)
|
||||
Enabled: enabled, // TODO include in response only when admin?
|
||||
// TODO query storage registry for free space? of home storage, maybe...
|
||||
Quota: &data.Quota{
|
||||
Free: 2840756224000,
|
||||
@@ -89,7 +125,6 @@ func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// AddUser creates a new user account
|
||||
func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO this endpoint needs authentication using the roles and permissions
|
||||
userid := r.PostFormValue("userid")
|
||||
password := r.PostFormValue("password")
|
||||
displayname := r.PostFormValue("displayname")
|
||||
@@ -150,9 +185,18 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusBadRequest {
|
||||
switch merr.Code {
|
||||
case http.StatusBadRequest:
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
|
||||
} else {
|
||||
case http.StatusConflict:
|
||||
if response.APIVersion(r.Context()) == "2" {
|
||||
// it seems the application framework sets the ocs status code to the httpstatus code, which affects the provisioning api
|
||||
// see https://github.com/owncloud/core/blob/b9ff4c93e051c94adfb301545098ae627e52ef76/lib/public/AppFramework/OCSController.php#L142-L150
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, merr.Detail))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaInvalidInput.StatusCode, merr.Detail))
|
||||
}
|
||||
default:
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", userid).Msg("could not add user")
|
||||
@@ -186,7 +230,6 @@ func (o Ocs) AddUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// EditUser creates a new user account
|
||||
func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO this endpoint needs authentication
|
||||
userid := chi.URLParam(r, "userid")
|
||||
account, err := o.fetchAccountByUsername(r.Context(), userid)
|
||||
if err != nil {
|
||||
@@ -239,7 +282,7 @@ func (o Ocs) EditUser(w http.ResponseWriter, r *http.Request) {
|
||||
default:
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", req.Account.Id).Msg("could not edit user")
|
||||
o.logger.Error().Err(err).Str("account_id", req.Account.Id).Str("user_id", userid).Msg("could not edit user")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -287,6 +330,86 @@ func (o Ocs) DeleteUser(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
// EnableUser enables a user
|
||||
func (o Ocs) EnableUser(w http.ResponseWriter, r *http.Request) {
|
||||
userid := chi.URLParam(r, "userid")
|
||||
account, err := o.fetchAccountByUsername(r.Context(), userid)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", userid).Msg("could not enable user")
|
||||
return
|
||||
}
|
||||
|
||||
account.AccountEnabled = true
|
||||
|
||||
req := accounts.UpdateAccountRequest{
|
||||
Account: account,
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"AccountEnabled"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = o.getAccountService().UpdateAccount(r.Context(), &req)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested account could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("account_id", account.Id).Msg("could not enable account")
|
||||
return
|
||||
}
|
||||
|
||||
o.logger.Debug().Str("account_id", account.Id).Msg("enabled user")
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
// DisableUser disables a user
|
||||
func (o Ocs) DisableUser(w http.ResponseWriter, r *http.Request) {
|
||||
userid := chi.URLParam(r, "userid")
|
||||
account, err := o.fetchAccountByUsername(r.Context(), userid)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested user could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("userid", userid).Msg("could not disable user")
|
||||
return
|
||||
}
|
||||
|
||||
account.AccountEnabled = false
|
||||
|
||||
req := accounts.UpdateAccountRequest{
|
||||
Account: account,
|
||||
UpdateMask: &field_mask.FieldMask{
|
||||
Paths: []string{"AccountEnabled"},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = o.getAccountService().UpdateAccount(r.Context(), &req)
|
||||
if err != nil {
|
||||
merr := merrors.FromError(err)
|
||||
if merr.Code == http.StatusNotFound {
|
||||
render.Render(w, r, response.ErrRender(data.MetaNotFound.StatusCode, "The requested account could not be found"))
|
||||
} else {
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, err.Error()))
|
||||
}
|
||||
o.logger.Error().Err(err).Str("account_id", account.Id).Msg("could not disable account")
|
||||
return
|
||||
}
|
||||
|
||||
o.logger.Debug().Str("account_id", account.Id).Msg("disabled user")
|
||||
render.Render(w, r, response.DataRender(struct{}{}))
|
||||
}
|
||||
|
||||
// GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist
|
||||
// The signing key is part of the user settings and is used by the proxy to authenticate requests
|
||||
// Currently, the username is used as the OC-Credential
|
||||
@@ -378,7 +501,7 @@ func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
users := []string{}
|
||||
for i := range res.Accounts {
|
||||
users = append(users, res.Accounts[i].Id)
|
||||
users = append(users, res.Accounts[i].OnPremisesSamAccountName)
|
||||
}
|
||||
|
||||
render.Render(w, r, response.DataRender(&data.Users{Users: users}))
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Enhancement: Add basic auth option
|
||||
|
||||
We added a new `enable-basic-auth` option and `PROXY_ENABLE_BASIC_AUTH` environment variable that can be set to `true` to make the proxy verify the basic auth header with the accounts service. This should only be used for testing and development and is disabled by default.
|
||||
|
||||
https://github.com/owncloud/ocis/pull/627
|
||||
https://github.com/owncloud/product/issues/198
|
||||
+107
-480
File diff suppressed because it is too large
Load Diff
@@ -263,6 +263,8 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic
|
||||
middleware.AccountsClient(accounts),
|
||||
middleware.SettingsRoleService(roles),
|
||||
middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts),
|
||||
middleware.EnableBasicAuth(cfg.EnableBasicAuth),
|
||||
middleware.OIDCIss(cfg.OIDC.Issuer),
|
||||
)
|
||||
|
||||
// the connection will be established in a non blocking fashion
|
||||
|
||||
@@ -99,6 +99,7 @@ type Config struct {
|
||||
Reva Reva
|
||||
PreSignedURL PreSignedURL
|
||||
AutoprovisionAccounts bool
|
||||
EnableBasicAuth bool
|
||||
}
|
||||
|
||||
// OIDC is the config for the OpenID-Connect middleware. If set the proxy will try to authenticate every request
|
||||
|
||||
@@ -219,6 +219,15 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag {
|
||||
Usage: "--presignedurl-allow-method GET [--presignedurl-allow-method POST]",
|
||||
EnvVars: []string{"PRESIGNEDURL_ALLOWED_METHODS"},
|
||||
},
|
||||
|
||||
// Basic auth
|
||||
&cli.BoolFlag{
|
||||
Name: "enable-basic-auth",
|
||||
Value: false,
|
||||
Usage: "enable basic authentication",
|
||||
EnvVars: []string{"PROXY_ENABLE_BASIC_AUTH"},
|
||||
Destination: &cfg.EnableBasicAuth,
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,6 +72,8 @@ func createAccount(l log.Logger, claims *oidc.StandardClaims, ac acc.AccountsSer
|
||||
func AccountUUID(opts ...Option) func(next http.Handler) http.Handler {
|
||||
opt := newOptions(opts...)
|
||||
|
||||
publicFilesEndpoint := "/remote.php/dav/public-files/"
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
// TODO: handle error
|
||||
tokenManager, err := jwt.New(map[string]interface{}{
|
||||
@@ -85,22 +87,43 @@ func AccountUUID(opts ...Option) func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
l := opt.Logger
|
||||
claims := oidc.FromContext(r.Context())
|
||||
if claims == nil {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
var account *acc.Account
|
||||
var status int
|
||||
if claims.Email != "" {
|
||||
switch {
|
||||
case claims == nil:
|
||||
login, password, ok := r.BasicAuth()
|
||||
// check if we are dealing with a public link
|
||||
if ok && login == "public" && strings.HasPrefix(r.URL.Path, publicFilesEndpoint) {
|
||||
// forward to reva frontend
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if opt.EnableBasicAuth && ok {
|
||||
l.Warn().Msg("basic auth enabled, use only for testing or development")
|
||||
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("login eq '%s' and password eq '%s'", strings.ReplaceAll(login, "'", "''"), strings.ReplaceAll(password, "'", "''")))
|
||||
if status == 0 {
|
||||
// fake claims for the subsequent code flow
|
||||
claims = &oidc.StandardClaims{
|
||||
Iss: opt.OIDCIss,
|
||||
}
|
||||
} else {
|
||||
// tell client to reauthenticate
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
case claims.Email != "":
|
||||
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("mail eq '%s'", strings.ReplaceAll(claims.Email, "'", "''")))
|
||||
} else if claims.PreferredUsername != "" {
|
||||
case claims.PreferredUsername != "":
|
||||
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("preferred_name eq '%s'", strings.ReplaceAll(claims.PreferredUsername, "'", "''")))
|
||||
} else if claims.OcisID != "" {
|
||||
case claims.OcisID != "":
|
||||
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("id eq '%s'", strings.ReplaceAll(claims.OcisID, "'", "''")))
|
||||
} else {
|
||||
default:
|
||||
// TODO allow lookup by custom claim, eg an id ... or sub
|
||||
l.Error().Err(err).Msgf("Could not lookup account, no mail or preferred_username claim set")
|
||||
l.Error().Err(err).Msg("Could not lookup account, no mail or preferred_username claim set")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
if status != 0 || account == nil {
|
||||
|
||||
@@ -39,6 +39,8 @@ type Options struct {
|
||||
PreSignedURLConfig config.PreSignedURL
|
||||
// AutoprovisionAccounts when an account does not exist.
|
||||
AutoprovisionAccounts bool
|
||||
// EnableBasicAuth to allow basic auth
|
||||
EnableBasicAuth bool
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -128,3 +130,10 @@ func AutoprovisionAccounts(val bool) Option {
|
||||
o.AutoprovisionAccounts = val
|
||||
}
|
||||
}
|
||||
|
||||
// EnableBasicAuth provides a function to set the EnableBasicAuth config
|
||||
func EnableBasicAuth(enableBasicAuth bool) Option {
|
||||
return func(o *Options) {
|
||||
o.EnableBasicAuth = enableBasicAuth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ func defaultPolicies() []config.Policy {
|
||||
},
|
||||
{
|
||||
Type: config.RegexRoute,
|
||||
Endpoint: "/ocs/v[12].php/cloud/user", // we have `user` and `users` in ocis-ocs
|
||||
Endpoint: "/ocs/v[12].php/cloud/(users?|groups)", // we have `user`, `users` and `groups` in ocis-ocs
|
||||
Backend: "http://localhost:9110",
|
||||
},
|
||||
{
|
||||
|
||||
@@ -145,6 +145,9 @@ func TestProxyIntegration(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal("Error reading result body")
|
||||
}
|
||||
if err = rr.Result().Body.Close(); err != nil {
|
||||
t.Fatal("Error closing result body")
|
||||
}
|
||||
|
||||
bodyString := string(resultBody)
|
||||
if bodyString != `OK` {
|
||||
|
||||
Reference in New Issue
Block a user