Add graph /me/changePassword endpoint

To allow users to change their own password when using the Graph LDAP backend.

Closes:	#3063
This commit is contained in:
Ralf Haferkamp
2022-05-05 18:05:25 +02:00
committed by Ralf Haferkamp
parent cbd7ea77f7
commit c41cf92553
13 changed files with 648 additions and 44 deletions

View File

@@ -28,6 +28,7 @@ ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this
$(MOCKERY) --dir pkg/service/v0 --case underscore --name GatewayClient
$(MOCKERY) --dir pkg/service/v0 --case underscore --name HTTPClient
$(MOCKERY) --dir pkg/service/v0 --case underscore --name Publisher
$(MOCKERY) --srcpkg github.com/go-ldap/ldap/v3 --case underscore --filename ldapclient.go --name Client
.PHONY: ci-node-generate

View File

@@ -18,6 +18,36 @@ type GatewayClient struct {
mock.Mock
}
// Authenticate provides a mock function with given fields: ctx, in, opts
func (_m *GatewayClient) Authenticate(ctx context.Context, in *gatewayv1beta1.AuthenticateRequest, opts ...grpc.CallOption) (*gatewayv1beta1.AuthenticateResponse, error) {
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r0 *gatewayv1beta1.AuthenticateResponse
if rf, ok := ret.Get(0).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) *gatewayv1beta1.AuthenticateResponse); ok {
r0 = rf(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*gatewayv1beta1.AuthenticateResponse)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *gatewayv1beta1.AuthenticateRequest, ...grpc.CallOption) error); ok {
r1 = rf(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// CreateStorageSpace provides a mock function with given fields: ctx, in, opts
func (_m *GatewayClient) CreateStorageSpace(ctx context.Context, in *providerv1beta1.CreateStorageSpaceRequest, opts ...grpc.CallOption) (*providerv1beta1.CreateStorageSpaceResponse, error) {
_va := make([]interface{}, len(opts))

View File

@@ -0,0 +1,294 @@
// Code generated by mockery v2.10.4. DO NOT EDIT.
package mocks
import (
ldap "github.com/go-ldap/ldap/v3"
mock "github.com/stretchr/testify/mock"
time "time"
tls "crypto/tls"
)
// Client is an autogenerated mock type for the Client type
type Client struct {
mock.Mock
}
// Add provides a mock function with given fields: _a0
func (_m *Client) Add(_a0 *ldap.AddRequest) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*ldap.AddRequest) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// Bind provides a mock function with given fields: username, password
func (_m *Client) Bind(username string, password string) error {
ret := _m.Called(username, password)
var r0 error
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(username, password)
} else {
r0 = ret.Error(0)
}
return r0
}
// Close provides a mock function with given fields:
func (_m *Client) Close() {
_m.Called()
}
// Compare provides a mock function with given fields: dn, attribute, value
func (_m *Client) Compare(dn string, attribute string, value string) (bool, error) {
ret := _m.Called(dn, attribute, value)
var r0 bool
if rf, ok := ret.Get(0).(func(string, string, string) bool); ok {
r0 = rf(dn, attribute, value)
} else {
r0 = ret.Get(0).(bool)
}
var r1 error
if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
r1 = rf(dn, attribute, value)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Del provides a mock function with given fields: _a0
func (_m *Client) Del(_a0 *ldap.DelRequest) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*ldap.DelRequest) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// ExternalBind provides a mock function with given fields:
func (_m *Client) ExternalBind() error {
ret := _m.Called()
var r0 error
if rf, ok := ret.Get(0).(func() error); ok {
r0 = rf()
} else {
r0 = ret.Error(0)
}
return r0
}
// IsClosing provides a mock function with given fields:
func (_m *Client) IsClosing() bool {
ret := _m.Called()
var r0 bool
if rf, ok := ret.Get(0).(func() bool); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(bool)
}
return r0
}
// Modify provides a mock function with given fields: _a0
func (_m *Client) Modify(_a0 *ldap.ModifyRequest) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*ldap.ModifyRequest) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// ModifyDN provides a mock function with given fields: _a0
func (_m *Client) ModifyDN(_a0 *ldap.ModifyDNRequest) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*ldap.ModifyDNRequest) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// ModifyWithResult provides a mock function with given fields: _a0
func (_m *Client) ModifyWithResult(_a0 *ldap.ModifyRequest) (*ldap.ModifyResult, error) {
ret := _m.Called(_a0)
var r0 *ldap.ModifyResult
if rf, ok := ret.Get(0).(func(*ldap.ModifyRequest) *ldap.ModifyResult); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*ldap.ModifyResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*ldap.ModifyRequest) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// PasswordModify provides a mock function with given fields: _a0
func (_m *Client) PasswordModify(_a0 *ldap.PasswordModifyRequest) (*ldap.PasswordModifyResult, error) {
ret := _m.Called(_a0)
var r0 *ldap.PasswordModifyResult
if rf, ok := ret.Get(0).(func(*ldap.PasswordModifyRequest) *ldap.PasswordModifyResult); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*ldap.PasswordModifyResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*ldap.PasswordModifyRequest) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Search provides a mock function with given fields: _a0
func (_m *Client) Search(_a0 *ldap.SearchRequest) (*ldap.SearchResult, error) {
ret := _m.Called(_a0)
var r0 *ldap.SearchResult
if rf, ok := ret.Get(0).(func(*ldap.SearchRequest) *ldap.SearchResult); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*ldap.SearchResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*ldap.SearchRequest) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SearchWithPaging provides a mock function with given fields: searchRequest, pagingSize
func (_m *Client) SearchWithPaging(searchRequest *ldap.SearchRequest, pagingSize uint32) (*ldap.SearchResult, error) {
ret := _m.Called(searchRequest, pagingSize)
var r0 *ldap.SearchResult
if rf, ok := ret.Get(0).(func(*ldap.SearchRequest, uint32) *ldap.SearchResult); ok {
r0 = rf(searchRequest, pagingSize)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*ldap.SearchResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*ldap.SearchRequest, uint32) error); ok {
r1 = rf(searchRequest, pagingSize)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// SetTimeout provides a mock function with given fields: _a0
func (_m *Client) SetTimeout(_a0 time.Duration) {
_m.Called(_a0)
}
// SimpleBind provides a mock function with given fields: _a0
func (_m *Client) SimpleBind(_a0 *ldap.SimpleBindRequest) (*ldap.SimpleBindResult, error) {
ret := _m.Called(_a0)
var r0 *ldap.SimpleBindResult
if rf, ok := ret.Get(0).(func(*ldap.SimpleBindRequest) *ldap.SimpleBindResult); ok {
r0 = rf(_a0)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*ldap.SimpleBindResult)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*ldap.SimpleBindRequest) error); ok {
r1 = rf(_a0)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Start provides a mock function with given fields:
func (_m *Client) Start() {
_m.Called()
}
// StartTLS provides a mock function with given fields: _a0
func (_m *Client) StartTLS(_a0 *tls.Config) error {
ret := _m.Called(_a0)
var r0 error
if rf, ok := ret.Get(0).(func(*tls.Config) error); ok {
r0 = rf(_a0)
} else {
r0 = ret.Error(0)
}
return r0
}
// UnauthenticatedBind provides a mock function with given fields: username
func (_m *Client) UnauthenticatedBind(username string) error {
ret := _m.Called(username)
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(username)
} else {
r0 = ret.Error(0)
}
return r0
}

View File

@@ -17,12 +17,14 @@ import (
"google.golang.org/grpc"
)
//go:generate make generate
//go:generate make -C ../../.. generate
// GatewayClient is the subset of the gateway.GatewayAPIClient that is being used to interact with the gateway
type GatewayClient interface {
//gateway.GatewayAPIClient
// Authenticates a user.
Authenticate(ctx context.Context, in *gateway.AuthenticateRequest, opts ...grpc.CallOption) (*gateway.AuthenticateResponse, error)
// Returns the home path for the given authenticated user.
// When a user has access to multiple storage providers, one of them is the home.
GetHome(ctx context.Context, in *provider.GetHomeRequest, opts ...grpc.CallOption) (*provider.GetHomeResponse, error)

View File

@@ -54,6 +54,11 @@ func (i instrument) PatchUser(w http.ResponseWriter, r *http.Request) {
i.next.PatchUser(w, r)
}
// ChangeOwnPassword implements the Service interface.
func (i instrument) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
i.next.ChangeOwnPassword(w, r)
}
// GetGroups implements the Service interface.
func (i instrument) GetGroups(w http.ResponseWriter, r *http.Request) {
i.next.GetGroups(w, r)

View File

@@ -54,6 +54,11 @@ func (l logging) PatchUser(w http.ResponseWriter, r *http.Request) {
l.next.PatchUser(w, r)
}
// ChangeOwnPassword implements the Service interface.
func (l logging) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
l.next.ChangeOwnPassword(w, r)
}
// GetGroups implements the Service interface.
func (l logging) GetGroups(w http.ResponseWriter, r *http.Request) {
l.next.GetGroups(w, r)

View File

@@ -5,6 +5,7 @@ import (
"github.com/cs3org/reva/v2/pkg/events"
"github.com/owncloud/ocis/v2/extensions/graph/pkg/config"
"github.com/owncloud/ocis/v2/extensions/graph/pkg/identity"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/owncloud/ocis/v2/ocis-pkg/roles"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
@@ -19,6 +20,7 @@ type Options struct {
Config *config.Config
Middleware []func(http.Handler) http.Handler
GatewayClient GatewayClient
IdentityBackend identity.Backend
HTTPClient HTTPClient
RoleService settingssvc.RoleService
RoleManager *roles.Manager
@@ -64,6 +66,13 @@ func WithGatewayClient(val GatewayClient) Option {
}
}
// WithIdentityBackend provides a function to set the IdentityBackend option.
func WithIdentityBackend(val identity.Backend) Option {
return func(o *Options) {
o.IdentityBackend = val
}
}
// WithHTTPClient provides a function to set the http client option.
func WithHTTPClient(val HTTPClient) Option {
return func(o *Options) {

View File

@@ -0,0 +1,88 @@
package svc
import (
"encoding/json"
"net/http"
"strings"
"github.com/CiscoM31/godata"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
cs3rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/go-chi/render"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0/errorcode"
)
// ChangeOwnPassword implements the Service interface. It allows the user to change
// its own password
func (g Graph) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
u, ok := revactx.ContextGetUser(ctx)
if !ok {
g.logger.Error().Msg("user not in context")
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, "user not in context")
return
}
sanitizedPath := strings.TrimPrefix(r.URL.Path, "/graph/v1.0/")
_, err := godata.ParseRequest(r.Context(), sanitizedPath, r.URL.Query())
if err != nil {
g.logger.Err(err).Interface("query", r.URL.Query()).Msg("query error")
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
return
}
cpw := libregraph.NewPasswordChange()
err = json.NewDecoder(r.Body).Decode(cpw)
if err != nil {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, err.Error())
return
}
currentPw := cpw.GetCurrentPassword()
if currentPw == "" {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "current password cannot be empty")
return
}
newPw := cpw.GetNewPassword()
if newPw == "" {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "new password cannot be empty")
return
}
if newPw == currentPw {
errorcode.InvalidRequest.Render(w, r, http.StatusBadRequest, "new password must be differnt from current password")
return
}
authReq := &gateway.AuthenticateRequest{
Type: "basic",
ClientId: u.Username,
ClientSecret: currentPw,
}
authRes, err := g.gatewayClient.Authenticate(r.Context(), authReq)
if err != nil {
errorcode.ServiceNotAvailable.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
if authRes.Status.Code != cs3rpc.Code_CODE_OK {
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "password change failed")
return
}
newPwProfile := libregraph.NewPasswordProfile()
newPwProfile.SetPassword(newPw)
changes := libregraph.NewUser()
changes.SetPasswordProfile(*newPwProfile)
_, err = g.identityBackend.UpdateUser(ctx, u.Id.OpaqueId, *changes)
if err != nil {
errorcode.InvalidRequest.Render(w, r, http.StatusInternalServerError, "password change failed")
g.logger.Debug().Err(err).Str("userid", u.Id.OpaqueId).Msg("failed to update user password")
return
}
render.Status(r, http.StatusNoContent)
render.NoContent(w, r)
}

View File

@@ -0,0 +1,159 @@
package svc_test
import (
"bytes"
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
revactx "github.com/cs3org/reva/v2/pkg/ctx"
"github.com/cs3org/reva/v2/pkg/rgrpc/status"
"github.com/go-ldap/ldap/v3"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
libregraph "github.com/owncloud/libre-graph-api-go"
"github.com/owncloud/ocis/v2/extensions/graph/mocks"
"github.com/owncloud/ocis/v2/extensions/graph/pkg/config"
"github.com/owncloud/ocis/v2/extensions/graph/pkg/config/defaults"
"github.com/owncloud/ocis/v2/extensions/graph/pkg/identity"
service "github.com/owncloud/ocis/v2/extensions/graph/pkg/service/v0"
"github.com/owncloud/ocis/v2/ocis-pkg/log"
"github.com/stretchr/testify/mock"
)
type changePwTest struct {
desc string
currentpw string
newpw string
expected int
}
var _ = Describe("Users changing their own password", func() {
var (
svc service.Service
gatewayClient *mocks.GatewayClient
httpClient *mocks.HTTPClient
ldapClient *mocks.Client
ldapConfig config.LDAP
identityBackend identity.Backend
eventsPublisher mocks.Publisher
ctx context.Context
cfg *config.Config
user *userv1beta1.User
err error
)
JustBeforeEach(func() {
ctx = context.Background()
cfg = defaults.FullDefaultConfig()
cfg.TokenManager.JWTSecret = "loremipsum"
gatewayClient = &mocks.GatewayClient{}
ldapClient = mockedLDAPClient()
ldapConfig = config.LDAP{
WriteEnabled: true,
UserDisplayNameAttribute: "displayName",
UserNameAttribute: "uid",
UserEmailAttribute: "mail",
UserIDAttribute: "ownclouduuid",
UserSearchScope: "sub",
GroupNameAttribute: "cn",
GroupIDAttribute: "ownclouduui",
GroupSearchScope: "sub",
}
loggger := log.NewLogger()
identityBackend, err = identity.NewLDAPBackend(ldapClient, ldapConfig, &loggger)
Expect(err).To(BeNil())
httpClient = &mocks.HTTPClient{}
eventsPublisher = mocks.Publisher{}
svc = service.NewService(
service.Config(cfg),
service.WithGatewayClient(gatewayClient),
service.WithIdentityBackend(identityBackend),
service.WithHTTPClient(httpClient),
service.EventsPublisher(&eventsPublisher),
)
user = &userv1beta1.User{
Id: &userv1beta1.UserId{
OpaqueId: "user",
},
}
ctx = revactx.ContextSetUser(ctx, user)
})
It("fails if no user in context", func() {
r := httptest.NewRequest(http.MethodGet, "/graph/v1.0/me/changePassword", nil)
rr := httptest.NewRecorder()
svc.ChangeOwnPassword(rr, r)
Expect(rr.Code).To(Equal(http.StatusInternalServerError))
})
DescribeTable("changing the password",
func(current string, newpw string, authresult string, expected int) {
switch authresult {
case "error":
gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(nil, errors.New("fail"))
case "deny":
gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
Status: status.NewPermissionDenied(ctx, errors.New("wrong password"), "wrong password"),
Token: "authtoken",
}, nil)
default:
gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{
Status: status.NewOK(ctx),
Token: "authtoken",
}, nil)
}
cpw := libregraph.NewPasswordChange()
cpw.SetCurrentPassword(current)
cpw.SetNewPassword(newpw)
body, _ := json.Marshal(cpw)
b := bytes.NewBuffer(body)
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/me/changePassword", b).WithContext(ctx)
rr := httptest.NewRecorder()
svc.ChangeOwnPassword(rr, r)
Expect(rr.Code).To(Equal(expected))
},
Entry("fails when current password is empty", "", "newpassword", "", http.StatusBadRequest),
Entry("fails when new password is empty", "currentpassword", "", "", http.StatusBadRequest),
Entry("fails when current and new password are equal", "password", "password", "", http.StatusBadRequest),
Entry("fails authentication with current password errors", "currentpassword", "newpassword", "error", http.StatusInternalServerError),
Entry("fails when current password is wrong", "currentpassword", "newpassword", "deny", http.StatusInternalServerError),
Entry("succeeds when current password is correct", "currentpassword", "newpassword", "", http.StatusNoContent),
)
})
func mockedLDAPClient() *mocks.Client {
lm := &mocks.Client{}
userEntry := ldap.NewEntry("uid=test", map[string][]string{
"uid": {"test"},
"displayName": {"test"},
"mail": {"test@example.org"},
})
lm.On("Search", mock.Anything, mock.Anything, mock.Anything, mock.Anything,
mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(
&ldap.SearchResult{Entries: []*ldap.Entry{userEntry}},
nil)
mr := ldap.NewModifyRequest("uid=test", nil)
mr.Changes = []ldap.Change{
{
Operation: ldap.ReplaceAttribute,
Modification: ldap.PartialAttribute{
Type: "userPassword",
Vals: []string{"newpassword"},
},
},
}
lm.On("Modify", mr).Return(nil)
return lm
}

View File

@@ -35,6 +35,7 @@ type Service interface {
PostUser(http.ResponseWriter, *http.Request)
DeleteUser(http.ResponseWriter, *http.Request)
PatchUser(http.ResponseWriter, *http.Request)
ChangeOwnPassword(http.ResponseWriter, *http.Request)
GetGroups(http.ResponseWriter, *http.Request)
GetGroup(http.ResponseWriter, *http.Request)
@@ -55,46 +56,10 @@ func NewService(opts ...Option) Service {
m := chi.NewMux()
m.Use(options.Middleware...)
var backend identity.Backend
switch options.Config.Identity.Backend {
case "cs3":
backend = &identity.CS3{
Config: options.Config.Reva,
Logger: &options.Logger,
}
case "ldap":
var err error
var tlsConf *tls.Config
if options.Config.Identity.LDAP.Insecure {
tlsConf = &tls.Config{
//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
}
}
conn := ldap.NewLDAPWithReconnect(&options.Logger,
ldap.Config{
URI: options.Config.Identity.LDAP.URI,
BindDN: options.Config.Identity.LDAP.BindDN,
BindPassword: options.Config.Identity.LDAP.BindPassword,
TLSConfig: tlsConf,
},
)
if backend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil {
options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
return nil
}
default:
options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
return nil
}
svc := Graph{
config: options.Config,
mux: m,
logger: &options.Logger,
identityBackend: backend,
spacePropertiesCache: ttlcache.NewCache(),
eventsPublisher: options.EventsPublisher,
}
@@ -108,6 +73,44 @@ func NewService(opts ...Option) Service {
} else {
svc.gatewayClient = options.GatewayClient
}
if options.IdentityBackend == nil {
switch options.Config.Identity.Backend {
case "cs3":
svc.identityBackend = &identity.CS3{
Config: options.Config.Reva,
Logger: &options.Logger,
}
case "ldap":
var err error
var tlsConf *tls.Config
if options.Config.Identity.LDAP.Insecure {
tlsConf = &tls.Config{
//nolint:gosec // We need the ability to run with "insecure" (dev/testing)
InsecureSkipVerify: options.Config.Identity.LDAP.Insecure,
}
}
conn := ldap.NewLDAPWithReconnect(&options.Logger,
ldap.Config{
URI: options.Config.Identity.LDAP.URI,
BindDN: options.Config.Identity.LDAP.BindDN,
BindPassword: options.Config.Identity.LDAP.BindPassword,
TLSConfig: tlsConf,
},
)
if svc.identityBackend, err = identity.NewLDAPBackend(conn, options.Config.Identity.LDAP, &options.Logger); err != nil {
options.Logger.Error().Msgf("Error initializing LDAP Backend: '%s'", err)
return nil
}
default:
options.Logger.Error().Msgf("Unknown Identity Backend: '%s'", options.Config.Identity.Backend)
return nil
}
} else {
svc.identityBackend = options.IdentityBackend
}
if options.HTTPClient == nil {
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: options.Config.Spaces.Insecure, //nolint:gosec
@@ -143,6 +146,7 @@ func NewService(opts ...Option) Service {
r.Get("/", svc.GetMe)
r.Get("/drives", svc.GetDrives)
r.Get("/drive/root/children", svc.GetRootDriveChildren)
r.Post("/changePassword", svc.ChangeOwnPassword)
})
r.Route("/users", func(r chi.Router) {
r.With(requireAdmin).Get("/", svc.GetUsers)

View File

@@ -50,6 +50,11 @@ func (t tracing) PatchUser(w http.ResponseWriter, r *http.Request) {
t.next.PatchUser(w, r)
}
// ChangeOwnPassword implements the Service interface.
func (t tracing) ChangeOwnPassword(w http.ResponseWriter, r *http.Request) {
t.next.ChangeOwnPassword(w, r)
}
// GetGroups implements the Service interface.
func (t tracing) GetGroups(w http.ResponseWriter, r *http.Request) {
t.next.GetGroups(w, r)

6
go.mod
View File

@@ -48,7 +48,7 @@ require (
github.com/onsi/ginkgo v1.16.5
github.com/onsi/ginkgo/v2 v2.1.4
github.com/onsi/gomega v1.19.0
github.com/owncloud/libre-graph-api-go v0.13.3
github.com/owncloud/libre-graph-api-go v0.14.2
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.1
github.com/rs/zerolog v1.26.1
@@ -67,8 +67,8 @@ require (
go.opentelemetry.io/otel/trace v1.7.0
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a
golang.org/x/net v0.0.0-20220516155154-20f960328961
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb
google.golang.org/grpc v1.46.0
google.golang.org/protobuf v1.28.0

10
go.sum
View File

@@ -1018,8 +1018,8 @@ github.com/oracle/oci-go-sdk v24.3.0+incompatible/go.mod h1:VQb79nF8Z2cwLkLS35uk
github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY=
github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI=
github.com/ovh/go-ovh v1.1.0/go.mod h1:AxitLZ5HBRPyUd+Zl60Ajaag+rNTdVXWIkzfrVuTXWA=
github.com/owncloud/libre-graph-api-go v0.13.3 h1:jNtQ8QcT7AZTfhdVHDaqAOs2xaJTfqfkucM9GARBIrQ=
github.com/owncloud/libre-graph-api-go v0.13.3/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
github.com/owncloud/libre-graph-api-go v0.14.2 h1:JiI32eDp7JZmiVv4aUpC4yaPpv6gK4xxM9MOe/0cXpE=
github.com/owncloud/libre-graph-api-go v0.14.2/go.mod h1:579sFrPP7aP24LZXGPopLfvE+hAka/2DYHk0+Ij+w+U=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@@ -1485,8 +1485,9 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220516155154-20f960328961 h1:+W/iTMPG0EL7aW+/atntZwZrvSRIj3m3yX414dSULUU=
golang.org/x/net v0.0.0-20220516155154-20f960328961/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1504,8 +1505,9 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM=
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE=
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=