Merge pull request #7973 from aduffeck/ocm-access

[full-ci] Ocm access
This commit is contained in:
Andre Duffeck
2023-12-14 14:36:31 +01:00
committed by GitHub
37 changed files with 1694 additions and 415 deletions

View File

@@ -0,0 +1,5 @@
Bugfix: Improve OCM support
We improved functionality of the OCM support.
https://github.com/owncloud/ocis/pull/7973

4
go.mod
View File

@@ -13,7 +13,7 @@ require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/coreos/go-oidc/v3 v3.9.0
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781
github.com/cs3org/reva/v2 v2.17.0
github.com/cs3org/reva/v2 v2.17.1-0.20231214082636-a8467337208a
github.com/dhowden/tag v0.0.0-20230630033851-978a0926ee25
github.com/disintegration/imaging v1.6.2
github.com/dutchcoders/go-clamd v0.0.0-20170520113014-b970184f4d9e
@@ -348,3 +348,5 @@ require (
)
replace github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348
replace github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20231123085457-ff658b6ea159

8
go.sum
View File

@@ -827,6 +827,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/aduffeck/gowebdav v0.0.0-20231123085457-ff658b6ea159 h1:m63hhLqbqmLGGPtyTtjTdxae61d9tMbRdKvMaDHWcDs=
github.com/aduffeck/gowebdav v0.0.0-20231123085457-ff658b6ea159/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@@ -1019,8 +1021,8 @@ github.com/crewjam/saml v0.4.14 h1:g9FBNx62osKusnFzs3QTN5L9CVA/Egfgm+stJShzw/c=
github.com/crewjam/saml v0.4.14/go.mod h1:UVSZCf18jJkk6GpWNVqcyQJMD5HsRugBPf4I1nl2mME=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781 h1:BUdwkIlf8IS2FasrrPg8gGPHQPOrQ18MS1Oew2tmGtY=
github.com/cs3org/go-cs3apis v0.0.0-20231023073225-7748710e0781/go.mod h1:UXha4TguuB52H14EMoSsCqDj7k8a/t7g4gVP+bgY5LY=
github.com/cs3org/reva/v2 v2.17.0 h1:cp7WXY+mZGLie4CKvIe3K+D/wG3sKVYrZJfs9Qnzioo=
github.com/cs3org/reva/v2 v2.17.0/go.mod h1:9hmBNVK+RSMSupWci9MQLmmj1NsJ8Bv49tqKbxMdxJY=
github.com/cs3org/reva/v2 v2.17.1-0.20231214082636-a8467337208a h1:ri98OWOuAY5tF3jpeKSItna3O4I0SedxzVgPQJPZXoM=
github.com/cs3org/reva/v2 v2.17.1-0.20231214082636-a8467337208a/go.mod h1:oX1YtLKGr7jatGk0CpPM4GKbSEIdHhmsQuSAYElnN1U=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -2000,8 +2002,6 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423 h1:Wd8WDEEusB5+En4PiRWJp1cP59QLNsQun+mOTW8+s6s=
github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes=

View File

@@ -142,6 +142,7 @@ type OCS struct {
ListOCMShares bool `yaml:"list_ocm_shares" env:"FRONTEND_OCS_LIST_OCM_SHARES" desc:"Include OCM shares when listing shares. See the OCM service documentation for more details."`
PublicShareMustHavePassword bool `yaml:"public_sharing_share_must_have_password" env:"OCIS_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on all public shares."`
WriteablePublicShareMustHavePassword bool `yaml:"public_sharing_writeableshare_must_have_password" env:"OCIS_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD;FRONTEND_OCS_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD" desc:"Set this to true if you want to enforce passwords on Uploader, Editor or Contributor shares."`
IncludeOCMSharees bool `yaml:"include_ocm_sharees" env:"FRONTEND_OCS_INCLUDE_OCM_SHAREES" desc:"Include OCM sharees when listing sharees."`
}
type CacheWarmupDrivers struct {

View File

@@ -117,6 +117,7 @@ func DefaultConfig() *config.Config {
StatCacheTTL: 300 * time.Second,
ListOCMShares: true,
PublicShareMustHavePassword: true,
IncludeOCMSharees: false,
},
Middleware: config.Middleware{
Auth: config.Auth{

View File

@@ -347,6 +347,7 @@ func FrontendConfigFromStruct(cfg *config.Config, logger log.Logger) (map[string
"productversion": version.GetString(),
},
},
"include_ocm_sharees": cfg.OCS.IncludeOCMSharees,
},
},
},

View File

@@ -185,6 +185,18 @@ func spacesProviders(cfg *config.Config, logger log.Logger) map[string]map[strin
},
},
},
cfg.OCMEndpoint: {
"providerid": utils.OCMStorageProviderID,
"spaces": map[string]interface{}{
"grant": map[string]interface{}{
"mount_point": ".",
},
"mountpoint": map[string]interface{}{
"mount_point": "/ocm",
"path_template": "/ocm/{{.Space.Root.OpaqueId}}",
},
},
},
// medatada storage not part of the global namespace
}
}

View File

@@ -31,6 +31,7 @@ type Config struct {
OCMProviderAuthorizerDrivers OCMProviderAuthorizerDrivers `yaml:"ocm_provider_authorizer_drivers"`
OCMShareProvider OCMShareProvider `yaml:"ocm_share_provider"`
OCMCore OCMCore `yaml:"ocm_core"`
OCMStorageProvider OCMStorageProvider `yaml:"ocm_storage_provider"`
Supervised bool `yaml:"-"`
Context context.Context `yaml:"-"`
@@ -72,7 +73,8 @@ type GRPCConfig struct {
}
type ScienceMesh struct {
Prefix string `yaml:"prefix" env:"OCM_SCIENCEMESH_PREFIX" desc:"URL path prefix for the ScienceMesh service. Note that the string must not start with '/'."`
Prefix string `yaml:"prefix" env:"OCM_SCIENCEMESH_PREFIX" desc:"URL path prefix for the ScienceMesh service. Note that the string must not start with '/'."`
MeshDirectoryURL string `yaml:"science_mesh_directory_url" env:"OCM_MESH_DIRECTORY_URL" desc:"URL of the mesh directory service."`
}
type OCMD struct {
@@ -107,6 +109,10 @@ type OCMCore struct {
Driver string `yaml:"driver" env:"OCM_OCM_CORE_DRIVER" desc:"Driver to be used for the OCM core. Supported value is only 'json'."`
Drivers OCMCoreDrivers `yaml:"drivers"`
}
type OCMStorageProvider struct {
Insecure bool `yaml:"insecure" env:"OCM_OCM_STORAGE_PROVIDER_INSECURE" desc:"Disable TLS certificate validation for the OCM connections. Do not set this in production environments."`
StorageRoot string `yaml:"storage_root" env:"OCM_OCM_STORAGE_PROVIDER_STORAGE_ROOT" desc:"Directory where the ocm storage provider persists its data like tus upload info files."`
}
type OCMCoreDrivers struct {
JSON OCMCoreJSONDriver `yaml:"json"`
@@ -117,9 +123,10 @@ type OCMCoreJSONDriver struct {
}
type OCMShareProvider struct {
Driver string `yaml:"driver" env:"OCM_OCM_SHARE_PROVIDER_DRIVER" desc:"Driver to be used for the OCM share provider. Supported value is only 'json'."`
Drivers OCMShareProviderDrivers `yaml:"drivers"`
Insecure bool `yaml:"insecure" env:"OCM_OCM_SHARE_PROVIDER_INSECURE" desc:"Disable TLS certificate validation for the OCM connections. Do not set this in production environments."`
Driver string `yaml:"driver" env:"OCM_OCM_SHARE_PROVIDER_DRIVER" desc:"Driver to be used for the OCM share provider. Supported value is only 'json'."`
Drivers OCMShareProviderDrivers `yaml:"drivers"`
Insecure bool `yaml:"insecure" env:"OCM_OCM_SHARE_PROVIDER_INSECURE" desc:"Disable TLS certificate validation for the OCM connections. Do not set this in production environments."`
WebappTemplate string `yaml:"webapp_template" env:"OCM_WEBAPP_TEMPLATE" desc:"Template for the webapp url."`
}
type OCMShareProviderDrivers struct {

View File

@@ -94,7 +94,7 @@ func DefaultConfig() *config.Config {
Driver: "json",
Drivers: config.OCMInviteManagerDrivers{
JSON: config.OCMInviteManagerJSONDriver{
File: filepath.Join(defaults.BaseDataPath(), "storage", "ocminvites.json"),
File: filepath.Join(defaults.BaseDataPath(), "storage", "ocm", "ocminvites.json"),
},
},
Insecure: false,
@@ -102,14 +102,14 @@ func DefaultConfig() *config.Config {
OCMProviderAuthorizerDriver: "json",
OCMProviderAuthorizerDrivers: config.OCMProviderAuthorizerDrivers{
JSON: config.OCMProviderAuthorizerJSONDriver{
Providers: filepath.Join(defaults.BaseDataPath(), "storage", "ocmproviders.json"),
Providers: filepath.Join(defaults.BaseDataPath(), "storage", "ocm", "ocmproviders.json"),
},
},
OCMShareProvider: config.OCMShareProvider{
Driver: "json",
Drivers: config.OCMShareProviderDrivers{
JSON: config.OCMShareProviderJSONDriver{
File: filepath.Join(defaults.BaseDataPath(), "storage", "ocmshares.json"),
File: filepath.Join(defaults.BaseDataPath(), "storage", "ocm", "ocmshares.json"),
},
},
Insecure: false,
@@ -118,10 +118,14 @@ func DefaultConfig() *config.Config {
Driver: "json",
Drivers: config.OCMCoreDrivers{
JSON: config.OCMCoreJSONDriver{
File: filepath.Join(defaults.BaseDataPath(), "storage", "ocmshares.json"),
File: filepath.Join(defaults.BaseDataPath(), "storage", "ocm", "ocmshares.json"),
},
},
},
OCMStorageProvider: config.OCMStorageProvider{
Insecure: false,
StorageRoot: filepath.Join(defaults.BaseDataPath(), "storage", "ocm"),
},
}
}

View File

@@ -43,7 +43,7 @@ func OCMConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]inter
"prefix": cfg.ScienceMesh.Prefix,
"smtp_credentials": map[string]string{},
"gatewaysvc": cfg.Reva.Address,
"mesh_directory_url": cfg.Commons.OcisURL,
"mesh_directory_url": cfg.ScienceMesh.MeshDirectoryURL,
"provider_domain": cfg.Commons.OcisURL,
},
"ocmd": map[string]interface{}{
@@ -51,6 +51,32 @@ func OCMConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]inter
"gatewaysvc": cfg.Reva.Address,
"expose_recipient_display_name": cfg.OCMD.ExposeRecipientDisplayName,
},
"dataprovider": map[string]interface{}{
"prefix": "data",
"driver": "ocmreceived",
"drivers": map[string]interface{}{
"ocmreceived": map[string]interface{}{
"insecure": cfg.OCMStorageProvider.Insecure,
},
},
"data_txs": map[string]interface{}{
"simple": map[string]interface{}{
"cache_store": "noop",
"cache_database": "system",
"cache_table": "stat",
},
"spaces": map[string]interface{}{
"cache_store": "noop",
"cache_database": "system",
"cache_table": "stat",
},
"tus": map[string]interface{}{
"cache_store": "noop",
"cache_database": "system",
"cache_table": "stat",
},
},
},
},
},
"grpc": map[string]interface{}{
@@ -91,6 +117,7 @@ func OCMConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]inter
"gatewaysvc": cfg.Reva.Address,
"provider_domain": cfg.Commons.OcisURL,
"webdav_endpoint": cfg.Commons.OcisURL,
"webapp_template": cfg.OCMShareProvider.WebappTemplate,
"client_insecure": cfg.OCMShareProvider.Insecure,
},
"ocmcore": map[string]interface{}{
@@ -101,6 +128,16 @@ func OCMConfigFromStruct(cfg *config.Config, logger log.Logger) map[string]inter
},
},
},
"storageprovider": map[string]interface{}{
"driver": "ocmreceived",
"drivers": map[string]interface{}{
"ocmreceived": map[string]interface{}{
"insecure": cfg.OCMStorageProvider.Insecure,
"storage_root": cfg.OCMStorageProvider.StorageRoot,
},
},
"data_server_url": "http://" + cfg.HTTP.Addr + "/data",
},
"authprovider": map[string]interface{}{
"auth_manager": "ocmshares",
"auth_managers": map[string]interface{}{

View File

@@ -25,6 +25,7 @@ var (
_publicPaths = [...]string{
"/dav/public-files/",
"/remote.php/dav/ocm/",
"/dav/ocm/",
"/ocm/",
"/remote.php/dav/public-files/",

View File

@@ -59,11 +59,18 @@ func (w *WebDAV) ToOCMProtocol() *ocm.Protocol {
switch p {
case "read":
perms.Permissions.GetPath = true
perms.Permissions.GetQuota = true
perms.Permissions.InitiateFileDownload = true
perms.Permissions.ListContainer = true
perms.Permissions.ListRecycle = true
perms.Permissions.Stat = true
case "write":
perms.Permissions.InitiateFileUpload = true
perms.Permissions.RestoreRecycleItem = true
perms.Permissions.CreateContainer = true
perms.Permissions.Delete = true
perms.Permissions.Move = true
perms.Permissions.ListGrants = true
case "share":
perms.Reshare = true
}

View File

@@ -1078,7 +1078,6 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
}
shareTypes = utils.ReadPlainFromOpaque(md.Opaque, "share-types")
}
role := conversions.RoleFromResourcePermissions(md.PermissionSet, ls != nil)
if md.Space != nil && md.Space.SpaceType != "grant" && utils.ResourceIDEqual(md.Space.Root, id) {
@@ -1174,6 +1173,7 @@ func mdToPropResponse(ctx context.Context, pf *XML, md *provider.ResourceInfo, p
if md.Name != "" {
appendToOK(prop.Escaped("oc:name", md.Name))
appendToOK(prop.Escaped("d:displayname", md.Name))
}
if md.Etag != "" {

View File

@@ -50,6 +50,7 @@ type Config struct {
OCMMountPoint string `mapstructure:"ocm_mount_point"`
ListOCMShares bool `mapstructure:"list_ocm_shares"`
Notifications map[string]interface{} `mapstructure:"notifications"`
IncludeOCMSharees bool `mapstructure:"include_ocm_sharees"`
}
// Init sets sane defaults

View File

@@ -24,6 +24,8 @@ import (
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
"github.com/cs3org/reva/v2/pkg/conversions"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/config"
@@ -37,12 +39,14 @@ import (
type Handler struct {
gatewayAddr string
additionalInfoAttribute string
includeOCMSharees bool
}
// Init initializes this and any contained handlers
func (h *Handler) Init(c *config.Config) {
h.gatewayAddr = c.GatewaySvc
h.additionalInfoAttribute = c.AdditionalInfoAttribute
h.includeOCMSharees = c.IncludeOCMSharees
}
// FindSharees implements the /apps/files_sharing/api/v1/sharees endpoint
@@ -79,6 +83,27 @@ func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) {
}
}
if h.includeOCMSharees {
remoteUsersRes, err := gwc.FindAcceptedUsers(r.Context(), &invitepb.FindAcceptedUsersRequest{Filter: term})
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching remote users", err)
return
}
if remoteUsersRes.Status.Code != rpc.Code_CODE_OK {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching remote users", nil)
return
}
for _, user := range remoteUsersRes.GetAcceptedUsers() {
match := h.userAsMatch(user)
log.Debug().Interface("user", user).Interface("match", match).Msg("mapped")
if h.isExactMatch(match, term) {
exactUserMatches = append(exactUserMatches, match)
} else {
userMatches = append(userMatches, match)
}
}
}
groupsRes, err := gwc.FindGroups(r.Context(), &grouppb.FindGroupsRequest{Filter: term, SkipFetchingMembers: true})
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error searching groups", err)
@@ -111,21 +136,27 @@ func (h *Handler) FindSharees(w http.ResponseWriter, r *http.Request) {
}
func (h *Handler) userAsMatch(u *userpb.User) *conversions.MatchData {
var ocsUserType int
if u.Id.Type == userpb.UserType_USER_TYPE_GUEST || u.Id.Type == userpb.UserType_USER_TYPE_LIGHTWEIGHT {
ocsUserType = 1
data := &conversions.MatchValueData{
ShareType: int(conversions.ShareTypeUser),
// api compatibility with oc10: mark guest users in share invite dialogue
UserType: 0,
// api compatibility with oc10: always use the username
ShareWith: u.Username,
ShareWithAdditionalInfo: h.getAdditionalInfoAttribute(u),
}
switch u.Id.Type {
case userpb.UserType_USER_TYPE_GUEST, userpb.UserType_USER_TYPE_LIGHTWEIGHT:
data.UserType = 1
case userpb.UserType_USER_TYPE_FEDERATED:
data.ShareType = int(conversions.ShareTypeFederatedCloudShare)
data.ShareWith = u.Id.OpaqueId
data.ShareWithProvider = u.Id.Idp
}
return &conversions.MatchData{
Label: u.DisplayName,
Value: &conversions.MatchValueData{
ShareType: int(conversions.ShareTypeUser),
// api compatibility with oc10: mark guest users in share invite dialogue
UserType: ocsUserType,
// api compatibility with oc10: always use the username
ShareWith: u.Username,
ShareWithAdditionalInfo: h.getAdditionalInfoAttribute(u),
},
Value: data,
}
}

View File

@@ -31,6 +31,7 @@ import (
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/response"
"github.com/cs3org/reva/v2/pkg/appctx"
@@ -50,6 +51,12 @@ const (
func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
shareID := chi.URLParam(r, shareidkey)
if h.isFederatedReceivedShare(r, shareID) {
h.updateReceivedFederatedShare(w, r, shareID, false)
return
}
client, err := h.getClient()
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err)
@@ -148,6 +155,12 @@ func (h *Handler) AcceptReceivedShare(w http.ResponseWriter, r *http.Request) {
// RejectReceivedShare handles DELETE Requests on /apps/files_sharing/api/v1/shares/{shareid}
func (h *Handler) RejectReceivedShare(w http.ResponseWriter, r *http.Request) {
shareID := chi.URLParam(r, "shareid")
if h.isFederatedReceivedShare(r, shareID) {
h.updateReceivedFederatedShare(w, r, shareID, true)
return
}
// we need to add a path to the share
receivedShare := &collaboration.ReceivedShare{
Share: &collaboration.Share{
@@ -254,6 +267,76 @@ func (h *Handler) updateReceivedShare(w http.ResponseWriter, r *http.Request, re
return data
}
func (h *Handler) updateReceivedFederatedShare(w http.ResponseWriter, r *http.Request, shareID string, rejectShare bool) {
ctx := r.Context()
client, err := h.getClient()
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err)
return
}
share, err := client.GetReceivedOCMShare(ctx, &ocmv1beta1.GetReceivedOCMShareRequest{
Ref: &ocmv1beta1.ShareReference{
Spec: &ocmv1beta1.ShareReference_Id{
Id: &ocmv1beta1.ShareId{
OpaqueId: shareID,
},
},
},
})
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", err)
return
}
if share.Status.Code != rpc.Code_CODE_OK {
if share.Status.Code == rpc.Code_CODE_NOT_FOUND {
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil)
return
}
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", errors.Errorf("code: %d, message: %s", share.Status.Code, share.Status.Message))
return
}
req := &ocmv1beta1.UpdateReceivedOCMShareRequest{
Share: &ocmv1beta1.ReceivedShare{
Id: &ocmv1beta1.ShareId{
OpaqueId: shareID,
},
},
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"state"}},
}
if rejectShare {
req.Share.State = ocmv1beta1.ShareState_SHARE_STATE_REJECTED
} else {
req.Share.State = ocmv1beta1.ShareState_SHARE_STATE_ACCEPTED
}
updateRes, err := client.UpdateReceivedOCMShare(ctx, req)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", err)
return
}
if updateRes.Status.Code != rpc.Code_CODE_OK {
if updateRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil)
return
}
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", errors.Errorf("code: %d, message: %s", updateRes.Status.Code, updateRes.Status.Message))
return
}
data, err := conversions.ReceivedOCMShare2ShareData(share.Share, h.ocmLocalMount(share.Share))
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc update received share request failed", err)
return
}
h.mapUserIdsReceivedFederatedShare(ctx, client, data)
data.State = mapOCMState(req.Share.State)
response.WriteOCSSuccess(w, r, []*conversions.ShareData{data})
}
// getReceivedShareHideFlagFromShareId returns the hide flag of a received share based on its ID.
func (h *Handler) getReceivedShareHideFlagFromShareID(ctx context.Context, shareID string) bool {
client, err := h.getClient()

View File

@@ -49,7 +49,7 @@ func (h *Handler) createFederatedCloudShare(w http.ResponseWriter, r *http.Reque
return
}
shareWithUser, shareWithProvider := r.FormValue("shareWithUser"), r.FormValue("shareWithProvider")
shareWithUser, shareWithProvider := r.FormValue("shareWith"), r.FormValue("shareWithProvider")
if shareWithUser == "" || shareWithProvider == "" {
response.WriteOCSError(w, r, response.MetaBadRequest.StatusCode, "missing shareWith parameters", nil)
return

View File

@@ -48,6 +48,7 @@ import (
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/types/known/fieldmaskpb"
ocm "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
ocmv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/ocm/v1beta1"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/config"
"github.com/cs3org/reva/v2/internal/http/services/owncloud/ocs/response"
@@ -642,6 +643,66 @@ func (h *Handler) GetShare(w http.ResponseWriter, r *http.Request) {
}
}
if share == nil {
// check if we have a federated share
req := &ocm.GetOCMShareRequest{
Ref: &ocm.ShareReference{
Spec: &ocm.ShareReference_Id{
Id: &ocm.ShareId{
OpaqueId: shareID,
},
},
},
}
ocmShareResponse, err := client.GetOCMShare(ctx, req)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get ocm share request", err)
return
}
ocmShare := ocmShareResponse.GetShare()
if ocmShare != nil {
resourceID = ocmShare.ResourceId
share, err = conversions.OCMShare2ShareData(ocmShare)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err)
return
}
}
}
if share == nil {
// check if we have an incoming federated share
req := &ocm.GetReceivedOCMShareRequest{
Ref: &ocm.ShareReference{
Spec: &ocm.ShareReference_Id{
Id: &ocm.ShareId{
OpaqueId: shareID,
},
},
},
}
ocmShareResponse, err := client.GetReceivedOCMShare(ctx, req)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc get ocm share request", err)
return
}
ocmShare := ocmShareResponse.GetShare()
if ocmShare != nil {
resourceID = &provider.ResourceId{
StorageId: utils.OCMStorageProviderID,
SpaceId: ocmShare.Id.OpaqueId,
OpaqueId: ocmShare.Id.OpaqueId,
}
share, err = conversions.ReceivedOCMShare2ShareData(ocmShare, h.ocmLocalMount(ocmShare))
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error mapping share data", err)
return
}
}
}
if share == nil {
sublog.Debug().Msg("no share found with this id")
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "share not found", nil)
@@ -857,6 +918,10 @@ func (h *Handler) RemoveShare(w http.ResponseWriter, r *http.Request) {
h.removeUserShare(w, r, share)
return
}
if h.isFederatedShare(r, shareID) {
h.removeFederatedShare(w, r, shareID)
return
}
if prov, ok := h.isSpaceShare(r, shareID); ok {
// The request is a remove space member request.

View File

@@ -155,6 +155,106 @@ func (h *Handler) isUserShare(r *http.Request, oid string) (*collaboration.Share
return getShareRes.GetShare(), getShareRes.GetShare() != nil
}
func (h *Handler) isFederatedShare(r *http.Request, shareID string) bool {
log := appctx.GetLogger(r.Context())
client, err := pool.GetGatewayServiceClient(h.gatewayAddr)
if err != nil {
log.Err(err).Send()
return false
}
getShareRes, err := client.GetOCMShare(r.Context(), &ocmpb.GetOCMShareRequest{
Ref: &ocmpb.ShareReference{
Spec: &ocmpb.ShareReference_Id{
Id: &ocmpb.ShareId{
OpaqueId: shareID,
},
},
},
})
if err != nil {
log.Err(err).Send()
return false
}
return getShareRes.GetShare() != nil
}
func (h *Handler) removeFederatedShare(w http.ResponseWriter, r *http.Request, shareID string) {
ctx := r.Context()
client, err := pool.GetGatewayServiceClient(h.gatewayAddr)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error getting grpc gateway client", err)
return
}
shareRef := &ocmpb.ShareReference_Id{Id: &ocmpb.ShareId{OpaqueId: shareID}}
// Get the share, so that we can include it in the response.
getShareResp, err := client.GetOCMShare(ctx, &ocmpb.GetOCMShareRequest{Ref: &ocmpb.ShareReference{Spec: shareRef}})
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc delete share request", err)
return
}
if getShareResp.Status.Code != rpc.Code_CODE_OK {
if getShareResp.Status.Code == rpc.Code_CODE_NOT_FOUND {
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil)
return
}
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "deleting share failed", err)
return
}
data, err := conversions.OCMShare2ShareData(getShareResp.Share)
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "deleting share failed", err)
return
}
// A deleted share should not have an ID.
data.ID = ""
uRes, err := client.RemoveOCMShare(ctx, &ocmpb.RemoveOCMShareRequest{Ref: &ocmpb.ShareReference{Spec: shareRef}})
if err != nil {
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "error sending a grpc delete share request", err)
return
}
if uRes.Status.Code != rpc.Code_CODE_OK {
if uRes.Status.Code == rpc.Code_CODE_NOT_FOUND {
response.WriteOCSError(w, r, response.MetaNotFound.StatusCode, "not found", nil)
return
}
response.WriteOCSError(w, r, response.MetaServerError.StatusCode, "grpc delete share request failed", err)
return
}
response.WriteOCSSuccess(w, r, data)
}
func (h *Handler) isFederatedReceivedShare(r *http.Request, shareID string) bool {
log := appctx.GetLogger(r.Context())
client, err := pool.GetGatewayServiceClient(h.gatewayAddr)
if err != nil {
log.Err(err).Send()
return false
}
getShareRes, err := client.GetReceivedOCMShare(r.Context(), &ocmpb.GetReceivedOCMShareRequest{
Ref: &ocmpb.ShareReference{
Spec: &ocmpb.ShareReference_Id{
Id: &ocmpb.ShareId{
OpaqueId: shareID,
},
},
},
})
if err != nil {
log.Err(err).Send()
return false
}
return getShareRes.GetShare() != nil
}
func (h *Handler) removeUserShare(w http.ResponseWriter, r *http.Request, share *collaboration.Share) {
ctx := r.Context()

View File

@@ -63,8 +63,8 @@ type config struct {
Prefix string `mapstructure:"prefix"`
SMTPCredentials *smtpclient.SMTPCredentials `mapstructure:"smtp_credentials" validate:"required"`
GatewaySvc string `mapstructure:"gatewaysvc" validate:"required"`
MeshDirectoryURL string `mapstructure:"mesh_directory_url" validate:"required"`
ProviderDomain string `mapstructure:"provider_domain" validate:"required"`
MeshDirectoryURL string `mapstructure:"mesh_directory_url"`
SubjectTemplate string `mapstructure:"subject_template"`
BodyTemplatePath string `mapstructure:"body_template_path"`
OCMMountPoint string `mapstructure:"ocm_mount_point"`

View File

@@ -76,7 +76,7 @@ type token struct {
Token string `json:"token"`
Description string `json:"description,omitempty"`
Expiration uint64 `json:"expiration,omitempty"`
InviteLink string `json:"invite_link"`
InviteLink string `json:"invite_link,omitempty"`
}
// Generate generates an invitation token and if a recipient is specified,
@@ -122,7 +122,9 @@ func (h *tokenHandler) prepareGenerateTokenResponse(tkn *invitepb.InviteToken) *
res := &token{
Token: tkn.Token,
Description: tkn.Description,
InviteLink: h.meshDirectoryURL + "?token=" + tkn.Token + "&providerDomain=" + h.providerDomain,
}
if h.meshDirectoryURL != "" {
res.InviteLink = h.meshDirectoryURL + "?token=" + tkn.Token + "&providerDomain=" + h.providerDomain
}
if tkn.Expiration != nil {
res.Expiration = tkn.Expiration.Seconds
@@ -187,7 +189,7 @@ func (h *tokenHandler) AcceptInvite(w http.ResponseWriter, r *http.Request) {
reqres.WriteError(w, r, reqres.APIErrorAlreadyExist, "user already known", nil)
return
case rpc.Code_CODE_PERMISSION_DENIED:
reqres.WriteError(w, r, reqres.APIErrorUnauthenticated, "remove service not trusted", nil)
reqres.WriteError(w, r, reqres.APIErrorUnauthenticated, "remote service not trusted", nil)
return
default:
reqres.WriteError(w, r, reqres.APIErrorServerError, "unexpected error: "+forwardInviteResponse.Status.Message, errors.New(forwardInviteResponse.Status.Message))

View File

@@ -21,15 +21,19 @@ package conversions
import (
"context"
"encoding/base64"
"fmt"
"net/http"
"path"
"path/filepath"
"time"
"github.com/cs3org/reva/v2/pkg/errtypes"
"github.com/cs3org/reva/v2/pkg/mime"
"github.com/cs3org/reva/v2/pkg/publicshare"
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/user"
"github.com/cs3org/reva/v2/pkg/utils"
grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1"
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
@@ -222,6 +226,7 @@ type MatchData struct {
type MatchValueData struct {
ShareType int `json:"shareType" xml:"shareType"`
ShareWith string `json:"shareWith" xml:"shareWith"`
ShareWithProvider string `json:"shareWithProvider" xml:"shareWithProvider"`
ShareWithAdditionalInfo string `json:"shareWithAdditionalInfo" xml:"shareWithAdditionalInfo,omitempty"`
UserType int `json:"userType" xml:"userType"`
}
@@ -325,6 +330,10 @@ func ReceivedOCMShare2ShareData(share *ocm.ReceivedShare, path string) (*ShareDa
return nil, errtypes.InternalError("webdav endpoint not in share")
}
opaqueid := base64.StdEncoding.EncodeToString([]byte("/"))
shareTarget := filepath.Join("/Shares", share.Name)
s := &ShareData{
ID: share.Id.OpaqueId,
UIDOwner: formatRemoteUser(share.Creator),
@@ -332,13 +341,18 @@ func ReceivedOCMShare2ShareData(share *ocm.ReceivedShare, path string) (*ShareDa
ShareWith: share.Grantee.GetUserId().OpaqueId,
Permissions: RoleFromResourcePermissions(webdav.GetPermissions().GetPermissions(), false).OCSPermissions(),
ShareType: ShareTypeFederatedCloudShare,
Path: path,
FileTarget: path,
Path: shareTarget,
FileTarget: shareTarget,
MimeType: mime.Detect(share.ResourceType == provider.ResourceType_RESOURCE_TYPE_CONTAINER, share.Name),
ItemType: ResourceType(share.ResourceType).String(),
ItemSource: path,
STime: share.Ctime.Seconds,
Name: share.Name,
ItemSource: storagespace.FormatResourceID(provider.ResourceId{
StorageId: utils.OCMStorageProviderID,
SpaceId: share.Id.OpaqueId,
OpaqueId: opaqueid,
}),
STime: share.Ctime.Seconds,
Name: share.Name,
SpaceID: storagespace.FormatStorageID(utils.OCMStorageProviderID, share.Id.OpaqueId),
}
if share.Expiration != nil {

View File

@@ -22,7 +22,9 @@ import (
"context"
"encoding/json"
"io"
"net/url"
"os"
"path/filepath"
"strings"
"sync"
"time"
@@ -89,6 +91,9 @@ func New(m map[string]interface{}) (invite.Repository, error) {
func loadOrCreate(file string) (*inviteModel, error) {
_, err := os.Stat(file)
if os.IsNotExist(err) {
if err := os.MkdirAll(filepath.Dir(file), 0700); err != nil {
return nil, errors.Wrap(err, "error creating the ocm storage dir: "+filepath.Dir(file))
}
if err := os.WriteFile(file, []byte("{}"), 0700); err != nil {
return nil, errors.Wrap(err, "error creating the invite storage file: "+file)
}
@@ -169,7 +174,7 @@ func (m *manager) ListTokens(ctx context.Context, initiator *userpb.UserId) ([]*
}
func tokenIsExpired(token *invitepb.InviteToken) bool {
return token.Expiration != nil && token.Expiration.Seconds > uint64(time.Now().Unix())
return token.Expiration != nil && token.Expiration.Seconds < uint64(time.Now().Unix())
}
func (m *manager) AddRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUser *userpb.User) error {
@@ -177,7 +182,7 @@ func (m *manager) AddRemoteUser(ctx context.Context, initiator *userpb.UserId, r
defer m.Unlock()
for _, acceptedUser := range m.model.AcceptedUsers[initiator.GetOpaqueId()] {
if acceptedUser.Id.GetOpaqueId() == remoteUser.Id.OpaqueId && acceptedUser.Id.GetIdp() == remoteUser.Id.Idp {
if acceptedUser.Id.GetOpaqueId() == remoteUser.Id.OpaqueId && idpsEqual(acceptedUser.Id.GetIdp(), remoteUser.Id.Idp) {
return invite.ErrUserAlreadyAccepted
}
}
@@ -189,6 +194,31 @@ func (m *manager) AddRemoteUser(ctx context.Context, initiator *userpb.UserId, r
return nil
}
func idpsEqual(idp1, idp2 string) bool {
normalizeIDP := func(s string) (string, error) {
u, err := url.Parse(s)
if err != nil {
return "", errors.New("could not parse url")
}
if u.Scheme == "" {
return strings.ToLower(u.Path), nil // the string is just a hostname
}
return strings.ToLower(u.Hostname()), nil
}
domain1, err := normalizeIDP(idp1)
if err != nil {
return false
}
domain2, err := normalizeIDP(idp2)
if err != nil {
return false
}
return domain1 == domain2
}
func (m *manager) GetRemoteUser(ctx context.Context, initiator *userpb.UserId, remoteUserID *userpb.UserId) (*userpb.User, error) {
m.RLock()
defer m.RUnlock()
@@ -201,7 +231,7 @@ func (m *manager) GetRemoteUser(ctx context.Context, initiator *userpb.UserId, r
acceptedUser.Id.GetOpaqueId(),
acceptedUser.Id.GetIdp(),
)
if (acceptedUser.Id.GetOpaqueId() == remoteUserID.OpaqueId) && (remoteUserID.Idp == "" || acceptedUser.Id.GetIdp() == remoteUserID.Idp) {
if (acceptedUser.Id.GetOpaqueId() == remoteUserID.OpaqueId) && (remoteUserID.Idp == "" || idpsEqual(acceptedUser.Id.GetIdp(), remoteUserID.Idp)) {
return acceptedUser, nil
}
}

View File

@@ -20,6 +20,8 @@ package ocm
import (
"context"
"crypto/tls"
"encoding/base64"
"io"
"io/fs"
"net/http"
@@ -58,12 +60,39 @@ type driver struct {
type config struct {
GatewaySVC string `mapstructure:"gatewaysvc"`
Insecure bool `mapstructure:"insecure"`
}
func (c *config) ApplyDefaults() {
c.GatewaySVC = sharedconf.GetGatewaySVC(c.GatewaySVC)
}
// BearerAuthenticator represents an authenticator that adds a Bearer token to the Authorization header of HTTP requests.
type BearerAuthenticator struct {
token string
}
// Authorize adds the Bearer token to the Authorization header of the provided HTTP request.
func (b BearerAuthenticator) Authorize(_ *http.Client, r *http.Request, _ string) error {
r.Header.Add("Authorization", "Bearer "+b.token)
return nil
}
// Verify is not implemented for the BearerAuthenticator. It always returns false and nil error.
func (BearerAuthenticator) Verify(*http.Client, *http.Response, string) (bool, error) {
return false, nil
}
// Clone creates a new instance of the BearerAuthenticator.
func (BearerAuthenticator) Clone() gowebdav.Authenticator {
return BearerAuthenticator{}
}
// Close is not implemented for the BearerAuthenticator. It always returns nil.
func (BearerAuthenticator) Close() error {
return nil
}
// New creates an OCM storage driver.
func New(m map[string]interface{}, _ events.Stream) (storage.FS, error) {
var c config
@@ -95,7 +124,16 @@ func shareInfoFromReference(ref *provider.Reference) (*ocmpb.ShareId, string) {
return shareInfoFromPath(ref.Path)
}
return &ocmpb.ShareId{OpaqueId: ref.ResourceId.OpaqueId}, ref.Path
if ref.ResourceId.SpaceId == ref.ResourceId.OpaqueId {
return &ocmpb.ShareId{OpaqueId: ref.ResourceId.SpaceId}, ref.Path
}
decodedBytes, err := base64.StdEncoding.DecodeString(ref.ResourceId.OpaqueId)
if err != nil {
// this should never happen
return &ocmpb.ShareId{OpaqueId: ref.ResourceId.SpaceId}, ref.Path
}
return &ocmpb.ShareId{OpaqueId: ref.ResourceId.SpaceId}, filepath.Join(string(decodedBytes), ref.Path)
}
func (d *driver) getWebDAVFromShare(ctx context.Context, shareID *ocmpb.ShareId) (*ocmpb.ReceivedShare, string, string, error) {
@@ -150,8 +188,12 @@ func (d *driver) webdavClient(ctx context.Context, ref *provider.Reference) (*go
// FIXME: it's still not clear from the OCM APIs how to use the shared secret
// will use as a token in the bearer authentication as this is the reva implementation
c := gowebdav.NewClient(endpoint, "", "")
c.SetHeader("Authorization", "Bearer "+secret)
c := gowebdav.NewAuthClient(endpoint, gowebdav.NewPreemptiveAuth(BearerAuthenticator{token: secret}))
if d.c.Insecure {
c.SetTransport(&http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
})
}
return c, share, rel, nil
}
@@ -194,7 +236,7 @@ func getPathFromShareIDAndRelPath(shareID *ocmpb.ShareId, relPath string) string
return filepath.Join("/", shareID.OpaqueId, relPath)
}
func convertStatToResourceInfo(ref *provider.Reference, f fs.FileInfo, share *ocmpb.ReceivedShare, relPath string) *provider.ResourceInfo {
func convertStatToResourceInfo(ref *provider.Reference, f fs.FileInfo, share *ocmpb.ReceivedShare) (*provider.ResourceInfo, error) {
t := provider.ResourceType_RESOURCE_TYPE_FILE
if f.IsDir() {
t = provider.ResourceType_RESOURCE_TYPE_CONTAINER
@@ -207,24 +249,36 @@ func convertStatToResourceInfo(ref *provider.Reference, f fs.FileInfo, share *oc
name = f.Name()
}
webdav, _ := getWebDAVProtocol(share.Protocols)
webdavFile, ok := f.(gowebdav.File)
if !ok {
return nil, errtypes.InternalError("could not get webdav props")
}
opaqueid := base64.StdEncoding.EncodeToString([]byte(webdavFile.Path()))
// ids are of the format <ocmstorageproviderid>$<shareid>!<opaqueid>
id := &provider.ResourceId{
StorageId: utils.OCMStorageProviderID,
SpaceId: share.Id.OpaqueId,
OpaqueId: opaqueid,
}
webdavProtocol, _ := getWebDAVProtocol(share.Protocols)
return &provider.ResourceInfo{
Type: t,
Id: ref.ResourceId,
Id: id,
MimeType: mime.Detect(f.IsDir(), f.Name()),
Path: relPath,
Path: name,
Name: name,
Size: uint64(f.Size()),
Mtime: &typepb.Timestamp{
Seconds: uint64(f.ModTime().Unix()),
},
Owner: share.Creator,
PermissionSet: webdav.Permissions.Permissions,
PermissionSet: webdavProtocol.Permissions.Permissions,
Checksum: &provider.ResourceChecksum{
Type: provider.ResourceChecksumType_RESOURCE_CHECKSUM_TYPE_INVALID,
},
}
}, nil
}
func (d *driver) GetMD(ctx context.Context, ref *provider.Reference, _ []string, _ []string) (*provider.ResourceInfo, error) {
@@ -233,7 +287,7 @@ func (d *driver) GetMD(ctx context.Context, ref *provider.Reference, _ []string,
return nil, err
}
info, err := client.Stat(rel)
info, err := client.StatWithProps(rel, []string{}) // request all properties by giving an empty list
if err != nil {
if gowebdav.IsErrNotFound(err) {
return nil, errtypes.NotFound(ref.GetPath())
@@ -241,7 +295,7 @@ func (d *driver) GetMD(ctx context.Context, ref *provider.Reference, _ []string,
return nil, err
}
return convertStatToResourceInfo(ref, info, share, rel), nil
return convertStatToResourceInfo(ref, info, share)
}
func (d *driver) ListFolder(ctx context.Context, ref *provider.Reference, _ []string, _ []string) ([]*provider.ResourceInfo, error) {
@@ -250,14 +304,18 @@ func (d *driver) ListFolder(ctx context.Context, ref *provider.Reference, _ []st
return nil, err
}
list, err := client.ReadDir(rel)
list, err := client.ReadDirWithProps(rel, []string{}) // request all properties by giving an empty list
if err != nil {
return nil, err
}
res := make([]*provider.ResourceInfo, 0, len(list))
for _, r := range list {
res = append(res, convertStatToResourceInfo(ref, r, share, utils.MakeRelativePath(filepath.Join(rel, r.Name()))))
info, err := convertStatToResourceInfo(ref, r, share)
if err != nil {
return nil, err
}
res = append(res, info)
}
return res, nil
}
@@ -429,7 +487,7 @@ func (d *driver) ListStorageSpaces(ctx context.Context, filters []*provider.List
if k == "mountpoint" {
for _, share := range lrsRes.Shares {
root := &provider.ResourceId{
StorageId: utils.PublicStorageProviderID,
StorageId: utils.OCMStorageProviderID,
SpaceId: share.Id.OpaqueId,
OpaqueId: share.Id.OpaqueId,
}

View File

@@ -59,6 +59,11 @@ var (
// PublicStorageSpaceID is the space id used by the sharestorageprovider
PublicStorageSpaceID = "7993447f-687f-490d-875c-ac95e89a62a4"
// OCMStorageProviderID is the storage id used by the ocmreceived storageprovider
OCMStorageProviderID = "89f37a33-858b-45fa-8890-a1f2b27d90e1"
// OCMStorageSpaceID is the space id used by the ocmreceived storageprovider
OCMStorageSpaceID = "89f37a33-858b-45fa-8890-a1f2b27d90e1"
// SpaceGrant is used to signal the storageprovider that the grant is on a space
SpaceGrant struct{}
)

View File

@@ -11,22 +11,31 @@ ${BIN}: ${SRC}
test:
go test -modfile=go_test.mod -v -short -cover ./...
api:
api: .go/bin/godoc2md
@sed '/^## API$$/,$$d' -i README.md
@echo '## API' >> README.md
@godoc2md github.com/studio-b12/gowebdav | sed '/^$$/N;/^\n$$/D' |\
@$< github.com/studio-b12/gowebdav | sed '/^$$/N;/^\n$$/D' |\
sed '2d' |\
sed 's/\/src\/github.com\/studio-b12\/gowebdav\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\
sed 's/\/src\/target\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\
sed 's/^#/##/g' >> README.md
check:
check: .go/bin/gocyclo
gofmt -w -s $(SRC)
@echo
gocyclo -over 15 .
.go/bin/gocyclo -over 15 .
@echo
go vet -modfile=go_test.mod ./...
.go/bin/godoc2md:
@mkdir -p $(@D)
@GOPATH="$(CURDIR)/.go" go install github.com/davecheney/godoc2md@latest
.go/bin/gocyclo:
@mkdir -p $(@D)
@GOPATH="$(CURDIR)/.go" go install github.com/fzipp/gocyclo/cmd/gocyclo@latest
clean:
@rm -f ${BIN}

View File

@@ -5,11 +5,12 @@
[![GoDoc](https://godoc.org/github.com/studio-b12/gowebdav?status.svg)](https://godoc.org/github.com/studio-b12/gowebdav)
[![Go Report Card](https://goreportcard.com/badge/github.com/studio-b12/gowebdav)](https://goreportcard.com/report/github.com/studio-b12/gowebdav)
A golang WebDAV client library.
A pure Golang WebDAV client library that comes with a [reference implementation](https://github.com/studio-b12/gowebdav/tree/master/cmd/gowebdav).
## Main features
## Features at a glance
Our `gowebdav` library allows to perform following actions on the remote WebDAV server:
`gowebdav` library allows to perform following actions on the remote WebDAV server:
* [create path](#create-path-on-a-webdav-server)
* [get files list](#get-files-list)
* [download file](#download-file-to-byte-array)
@@ -19,6 +20,17 @@ A golang WebDAV client library.
* [copy file to another location](#copy-file-to-another-location)
* [delete file](#delete-file)
It also provides an [authentication API](#type-authenticator) that makes it easy to encapsulate and control complex authentication challenges.
The default implementation negotiates the algorithm based on the user's preferences and the methods offered by the remote server.
Out-of-box authentication support for:
* [BasicAuth](https://en.wikipedia.org/wiki/Basic_access_authentication)
* [DigestAuth](https://en.wikipedia.org/wiki/Digest_access_authentication)
* [MS-PASS](https://github.com/studio-b12/gowebdav/pull/70#issuecomment-1421713726)
* [WIP Kerberos](https://github.com/studio-b12/gowebdav/pull/71#issuecomment-1416465334)
* [WIP Bearer Token](https://github.com/studio-b12/gowebdav/issues/61)
## Usage
First of all you should create `Client` instance using `NewClient()` function:
@@ -29,11 +41,13 @@ user := "user"
password := "password"
c := gowebdav.NewClient(root, user, password)
c.Connect()
// kick of your work!
```
After you can use this `Client` to perform actions, described below.
**NOTICE:** we will not check errors in examples, to focus you on the `gowebdav` library's code, but you should do it in your code!
**NOTICE:** We will not check for errors in the examples, to focus you on the `gowebdav` library's code, but you should do it in your code!
### Create path on a WebDAV server
```go
@@ -59,7 +73,7 @@ webdavFilePath := "folder/subfolder/file.txt"
localFilePath := "/tmp/webdav/file.txt"
bytes, _ := c.Read(webdavFilePath)
ioutil.WriteFile(localFilePath, bytes, 0644)
os.WriteFile(localFilePath, bytes, 0644)
```
### Download file via reader
@@ -81,7 +95,7 @@ io.Copy(file, reader)
webdavFilePath := "folder/subfolder/file.txt"
localFilePath := "/tmp/webdav/file.txt"
bytes, _ := ioutil.ReadFile(localFilePath)
bytes, _ := os.ReadFile(localFilePath)
c.Write(webdavFilePath, bytes, 0644)
```
@@ -161,21 +175,34 @@ Package gowebdav is a WebDAV client library with a command line tool
included.
### <a name="pkg-index">Index</a>
* [Constants](#pkg-constants)
* [Variables](#pkg-variables)
* [func FixSlash(s string) string](#FixSlash)
* [func FixSlashes(s string) string](#FixSlashes)
* [func IsErrCode(err error, code int) bool](#IsErrCode)
* [func IsErrNotFound(err error) bool](#IsErrNotFound)
* [func Join(path0 string, path1 string) string](#Join)
* [func NewPathError(op string, path string, statusCode int) error](#NewPathError)
* [func NewPathErrorErr(op string, path string, err error) error](#NewPathErrorErr)
* [func PathEscape(path string) string](#PathEscape)
* [func ReadConfig(uri, netrc string) (string, string)](#ReadConfig)
* [func String(r io.Reader) string](#String)
* [type AuthFactory](#AuthFactory)
* [type Authenticator](#Authenticator)
* [func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error)](#NewDigestAuth)
* [func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error)](#NewPassportAuth)
* [type Authorizer](#Authorizer)
* [func NewAutoAuth(login string, secret string) Authorizer](#NewAutoAuth)
* [func NewEmptyAuth() Authorizer](#NewEmptyAuth)
* [func NewPreemptiveAuth(auth Authenticator) Authorizer](#NewPreemptiveAuth)
* [type BasicAuth](#BasicAuth)
* [func (b *BasicAuth) Authorize(req *http.Request, method string, path string)](#BasicAuth.Authorize)
* [func (b *BasicAuth) Pass() string](#BasicAuth.Pass)
* [func (b *BasicAuth) Type() string](#BasicAuth.Type)
* [func (b *BasicAuth) User() string](#BasicAuth.User)
* [func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#BasicAuth.Authorize)
* [func (b *BasicAuth) Clone() Authenticator](#BasicAuth.Clone)
* [func (b *BasicAuth) Close() error](#BasicAuth.Close)
* [func (b *BasicAuth) String() string](#BasicAuth.String)
* [func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#BasicAuth.Verify)
* [type Client](#Client)
* [func NewAuthClient(uri string, auth Authorizer) *Client](#NewAuthClient)
* [func NewClient(uri, user, pw string) *Client](#NewClient)
* [func (c *Client) Connect() error](#Client.Connect)
* [func (c *Client) Copy(oldpath, newpath string, overwrite bool) error](#Client.Copy)
@@ -190,16 +217,18 @@ included.
* [func (c *Client) Rename(oldpath, newpath string, overwrite bool) error](#Client.Rename)
* [func (c *Client) SetHeader(key, value string)](#Client.SetHeader)
* [func (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request))](#Client.SetInterceptor)
* [func (c *Client) SetJar(jar http.CookieJar)](#Client.SetJar)
* [func (c *Client) SetTimeout(timeout time.Duration)](#Client.SetTimeout)
* [func (c *Client) SetTransport(transport http.RoundTripper)](#Client.SetTransport)
* [func (c *Client) Stat(path string) (os.FileInfo, error)](#Client.Stat)
* [func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error)](#Client.Write)
* [func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error)](#Client.WriteStream)
* [type DigestAuth](#DigestAuth)
* [func (d *DigestAuth) Authorize(req *http.Request, method string, path string)](#DigestAuth.Authorize)
* [func (d *DigestAuth) Pass() string](#DigestAuth.Pass)
* [func (d *DigestAuth) Type() string](#DigestAuth.Type)
* [func (d *DigestAuth) User() string](#DigestAuth.User)
* [func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#DigestAuth.Authorize)
* [func (d *DigestAuth) Clone() Authenticator](#DigestAuth.Clone)
* [func (d *DigestAuth) Close() error](#DigestAuth.Close)
* [func (d *DigestAuth) String() string](#DigestAuth.String)
* [func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#DigestAuth.Verify)
* [type File](#File)
* [func (f File) ContentType() string](#File.ContentType)
* [func (f File) ETag() string](#File.ETag)
@@ -211,11 +240,12 @@ included.
* [func (f File) Size() int64](#File.Size)
* [func (f File) String() string](#File.String)
* [func (f File) Sys() interface{}](#File.Sys)
* [type NoAuth](#NoAuth)
* [func (n *NoAuth) Authorize(req *http.Request, method string, path string)](#NoAuth.Authorize)
* [func (n *NoAuth) Pass() string](#NoAuth.Pass)
* [func (n *NoAuth) Type() string](#NoAuth.Type)
* [func (n *NoAuth) User() string](#NoAuth.User)
* [type PassportAuth](#PassportAuth)
* [func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#PassportAuth.Authorize)
* [func (p *PassportAuth) Clone() Authenticator](#PassportAuth.Clone)
* [func (p *PassportAuth) Close() error](#PassportAuth.Close)
* [func (p *PassportAuth) String() string](#PassportAuth.String)
* [func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#PassportAuth.Verify)
* [type StatusError](#StatusError)
* [func (se StatusError) Error() string](#StatusError.Error)
@@ -223,7 +253,24 @@ included.
* [PathEscape](#example_PathEscape)
##### <a name="pkg-files">Package files</a>
[basicAuth.go](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go) [client.go](https://github.com/studio-b12/gowebdav/blob/master/client.go) [digestAuth.go](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go) [doc.go](https://github.com/studio-b12/gowebdav/blob/master/doc.go) [errors.go](https://github.com/studio-b12/gowebdav/blob/master/errors.go) [file.go](https://github.com/studio-b12/gowebdav/blob/master/file.go) [netrc.go](https://github.com/studio-b12/gowebdav/blob/master/netrc.go) [requests.go](https://github.com/studio-b12/gowebdav/blob/master/requests.go) [utils.go](https://github.com/studio-b12/gowebdav/blob/master/utils.go)
[auth.go](https://github.com/studio-b12/gowebdav/blob/master/auth.go) [basicAuth.go](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go) [client.go](https://github.com/studio-b12/gowebdav/blob/master/client.go) [digestAuth.go](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go) [doc.go](https://github.com/studio-b12/gowebdav/blob/master/doc.go) [errors.go](https://github.com/studio-b12/gowebdav/blob/master/errors.go) [file.go](https://github.com/studio-b12/gowebdav/blob/master/file.go) [netrc.go](https://github.com/studio-b12/gowebdav/blob/master/netrc.go) [passportAuth.go](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go) [requests.go](https://github.com/studio-b12/gowebdav/blob/master/requests.go) [utils.go](https://github.com/studio-b12/gowebdav/blob/master/utils.go)
### <a name="pkg-constants">Constants</a>
``` go
const XInhibitRedirect = "X-Gowebdav-Inhibit-Redirect"
```
### <a name="pkg-variables">Variables</a>
``` go
var ErrAuthChanged = errors.New("authentication failed, change algorithm")
```
ErrAuthChanged must be returned from the Verify method as an error
to trigger a re-authentication / negotiation with a new authenticator.
``` go
var ErrTooManyRedirects = errors.New("stopped after 10 redirects")
```
ErrTooManyRedirects will be used as return error if a request exceeds 10 redirects.
### <a name="FixSlash">func</a> [FixSlash](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=354:384#L23)
``` go
@@ -237,7 +284,7 @@ func FixSlashes(s string) string
```
FixSlashes appends and prepends a / if they are missing
### <a name="IsErrCode">func</a> [IsErrCode](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=355:395#L21)
### <a name="IsErrCode">func</a> [IsErrCode](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=740:780#L29)
``` go
func IsErrCode(err error, code int) bool
```
@@ -245,7 +292,7 @@ IsErrCode returns true if the given error
is an os.PathError wrapping a StatusError
with the given status code.
### <a name="IsErrNotFound">func</a> [IsErrNotFound](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=587:621#L31)
### <a name="IsErrNotFound">func</a> [IsErrNotFound](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=972:1006#L39)
``` go
func IsErrNotFound(err error) bool
```
@@ -258,6 +305,16 @@ func Join(path0 string, path1 string) string
```
Join joins two paths
### <a name="NewPathError">func</a> [NewPathError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=1040:1103#L43)
``` go
func NewPathError(op string, path string, statusCode int) error
```
### <a name="NewPathErrorErr">func</a> [NewPathErrorErr](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=1194:1255#L51)
``` go
func NewPathErrorErr(op string, path string, err error) error
```
### <a name="PathEscape">func</a> [PathEscape](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=153:188#L14)
``` go
func PathEscape(path string) string
@@ -277,18 +334,119 @@ func String(r io.Reader) string
```
String pulls a string out of our io.Reader
### <a name="Authenticator">type</a> [Authenticator](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=388:507#L29)
### <a name="AuthFactory">type</a> [AuthFactory](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=150:251#L13)
``` go
type AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error)
```
AuthFactory prototype function to create a new Authenticator
### <a name="Authenticator">type</a> [Authenticator](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=2155:2695#L56)
``` go
type Authenticator interface {
Type() string
User() string
Pass() string
Authorize(*http.Request, string, string)
// Authorizes a request. Usually by adding some authorization headers.
Authorize(c *http.Client, rq *http.Request, path string) error
// Verifies the response if the authorization was successful.
// May trigger some round trips to pass the authentication.
// May also trigger a new Authenticator negotiation by returning `ErrAuthChenged`
Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)
// Creates a copy of the underlying Authenticator.
Clone() Authenticator
io.Closer
}
```
Authenticator stub
A Authenticator implements a specific way to authorize requests.
Each request is bound to a separate Authenticator instance.
### <a name="BasicAuth">type</a> [BasicAuth](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=106:157#L9)
The authentication flow itself is broken down into `Authorize`
and `Verify` steps. The former method runs before, and the latter
runs after the `Request` is submitted.
This makes it easy to encapsulate and control complex
authentication challenges.
Some authentication flows causing authentication round trips,
which can be archived by returning the `redo` of the Verify
method. `True` restarts the authentication process for the
current action: A new `Request` is spawned, which must be
authorized, sent, and re-verified again, until the action
is successfully submitted.
The preferred way is to handle the authentication ping-pong
within `Verify`, and then `redo` with fresh credentials.
The result of the `Verify` method can also trigger an
`Authenticator` change by returning the `ErrAuthChanged`
as an error. Depending on the `Authorizer` this may trigger
an `Authenticator` negotiation.
Set the `XInhibitRedirect` header to '1' in the `Authorize`
method to get control over request redirection.
Attention! You must handle the incoming request yourself.
To store a shared session state the `Clone` method **must**
return a new instance, initialized with the shared state.
#### <a name="NewDigestAuth">func</a> [NewDigestAuth](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=324:406#L21)
``` go
func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error)
```
NewDigestAuth creates a new instance of our Digest Authenticator
#### <a name="NewPassportAuth">func</a> [NewPassportAuth](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=386:495#L21)
``` go
func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error)
```
constructor for PassportAuth creates a new PassportAuth object and
automatically authenticates against the given partnerURL
### <a name="Authorizer">type</a> [Authorizer](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=349:764#L17)
``` go
type Authorizer interface {
// Creates a new Authenticator Shim per request.
// It may track request related states and perform payload buffering
// for authentication round trips.
// The underlying Authenticator will perform the real authentication.
NewAuthenticator(body io.Reader) (Authenticator, io.Reader)
// Registers a new Authenticator factory to a key.
AddAuthenticator(key string, fn AuthFactory)
}
```
Authorizer our Authenticator factory which creates an
`Authenticator` per action/request.
#### <a name="NewAutoAuth">func</a> [NewAutoAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=3789:3845#L109)
``` go
func NewAutoAuth(login string, secret string) Authorizer
```
NewAutoAuth creates an auto Authenticator factory.
It negotiates the default authentication method
based on the order of the registered Authenticators
and the remotely offered authentication methods.
First In, First Out.
#### <a name="NewEmptyAuth">func</a> [NewEmptyAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=4694:4724#L132)
``` go
func NewEmptyAuth() Authorizer
```
NewEmptyAuth creates an empty Authenticator factory
The order of adding the Authenticator matters.
First In, First Out.
It offers the `NewAutoAuth` features.
#### <a name="NewPreemptiveAuth">func</a> [NewPreemptiveAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=5300:5353#L148)
``` go
func NewPreemptiveAuth(auth Authenticator) Authorizer
```
NewPreemptiveAuth creates a preemptive Authenticator
The preemptive authorizer uses the provided Authenticator
for every request regardless of any `Www-Authenticate` header.
It may only have one authentication method,
so calling `AddAuthenticator` **will panic**!
Look out!! This offers the skinniest and slickest implementation
without any synchronisation!!
Still applicable with `BasicAuth` within go routines.
### <a name="BasicAuth">type</a> [BasicAuth](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=94:145#L9)
``` go
type BasicAuth struct {
// contains filtered or unexported fields
@@ -297,31 +455,37 @@ type BasicAuth struct {
```
BasicAuth structure holds our credentials
#### <a name="BasicAuth.Authorize">func</a> (\*BasicAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=473:549#L30)
#### <a name="BasicAuth.Authorize">func</a> (\*BasicAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=180:262#L15)
``` go
func (b *BasicAuth) Authorize(req *http.Request, method string, path string)
func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error
```
Authorize the current request
#### <a name="BasicAuth.Pass">func</a> (\*BasicAuth) [Pass](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=388:421#L25)
#### <a name="BasicAuth.Clone">func</a> (\*BasicAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=666:707#L34)
``` go
func (b *BasicAuth) Pass() string
func (b *BasicAuth) Clone() Authenticator
```
Pass holds the BasicAuth password
Clone creates a Copy of itself
#### <a name="BasicAuth.Type">func</a> (\*BasicAuth) [Type](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=201:234#L15)
#### <a name="BasicAuth.Close">func</a> (\*BasicAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=581:614#L29)
``` go
func (b *BasicAuth) Type() string
func (b *BasicAuth) Close() error
```
Type identifies the BasicAuthenticator
Close cleans up all resources
#### <a name="BasicAuth.User">func</a> (\*BasicAuth) [User](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=297:330#L20)
#### <a name="BasicAuth.String">func</a> (\*BasicAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=778:813#L40)
``` go
func (b *BasicAuth) User() string
func (b *BasicAuth) String() string
```
User holds the BasicAuth username
String toString
### <a name="Client">type</a> [Client](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=172:364#L18)
#### <a name="BasicAuth.Verify">func</a> (\*BasicAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=352:449#L21)
``` go
func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)
```
Verify verifies if the authentication
### <a name="Client">type</a> [Client](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=220:388#L19)
``` go
type Client struct {
// contains filtered or unexported fields
@@ -330,55 +494,61 @@ type Client struct {
```
Client defines our structure
#### <a name="NewClient">func</a> [NewClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1019:1063#L62)
#### <a name="NewAuthClient">func</a> [NewAuthClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=608:663#L33)
``` go
func NewAuthClient(uri string, auth Authorizer) *Client
```
NewAuthClient creates a new client instance with a custom Authorizer
#### <a name="NewClient">func</a> [NewClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=436:480#L28)
``` go
func NewClient(uri, user, pw string) *Client
```
NewClient creates a new instance of client
#### <a name="Client.Connect">func</a> (\*Client) [Connect](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1843:1875#L87)
#### <a name="Client.Connect">func</a> (\*Client) [Connect](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1829:1861#L74)
``` go
func (c *Client) Connect() error
```
Connect connects to our dav server
#### <a name="Client.Copy">func</a> (\*Client) [Copy](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6818:6886#L323)
#### <a name="Client.Copy">func</a> (\*Client) [Copy](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6815:6883#L310)
``` go
func (c *Client) Copy(oldpath, newpath string, overwrite bool) error
```
Copy copies a file from A to B
#### <a name="Client.Mkdir">func</a> (\*Client) [Mkdir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5793:5855#L272)
#### <a name="Client.Mkdir">func</a> (\*Client) [Mkdir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5790:5852#L259)
``` go
func (c *Client) Mkdir(path string, _ os.FileMode) (err error)
```
Mkdir makes a directory
#### <a name="Client.MkdirAll">func</a> (\*Client) [MkdirAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6068:6133#L286)
#### <a name="Client.MkdirAll">func</a> (\*Client) [MkdirAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6065:6130#L273)
``` go
func (c *Client) MkdirAll(path string, _ os.FileMode) (err error)
```
MkdirAll like mkdir -p, but for webdav
#### <a name="Client.Read">func</a> (\*Client) [Read](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6992:7042#L328)
#### <a name="Client.Read">func</a> (\*Client) [Read](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6989:7039#L315)
``` go
func (c *Client) Read(path string) ([]byte, error)
```
Read reads the contents of a remote file
#### <a name="Client.ReadDir">func</a> (\*Client) [ReadDir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=2869:2929#L130)
#### <a name="Client.ReadDir">func</a> (\*Client) [ReadDir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=2855:2915#L117)
``` go
func (c *Client) ReadDir(path string) ([]os.FileInfo, error)
```
ReadDir reads the contents of a remote directory
#### <a name="Client.ReadStream">func</a> (\*Client) [ReadStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=7353:7416#L346)
#### <a name="Client.ReadStream">func</a> (\*Client) [ReadStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=7350:7413#L333)
``` go
func (c *Client) ReadStream(path string) (io.ReadCloser, error)
```
ReadStream reads the stream for a given path
#### <a name="Client.ReadStreamRange">func</a> (\*Client) [ReadStreamRange](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=8165:8255#L368)
#### <a name="Client.ReadStreamRange">func</a> (\*Client) [ReadStreamRange](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=8162:8252#L355)
``` go
func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error)
```
@@ -391,61 +561,67 @@ If the server does not support partial content requests and returns full content
this function will emulate the behavior by skipping `offset` bytes and limiting the result
to `length`.
#### <a name="Client.Remove">func</a> (\*Client) [Remove](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5299:5341#L249)
#### <a name="Client.Remove">func</a> (\*Client) [Remove](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5296:5338#L236)
``` go
func (c *Client) Remove(path string) error
```
Remove removes a remote file
#### <a name="Client.RemoveAll">func</a> (\*Client) [RemoveAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5407:5452#L254)
#### <a name="Client.RemoveAll">func</a> (\*Client) [RemoveAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5404:5449#L241)
``` go
func (c *Client) RemoveAll(path string) error
```
RemoveAll removes remote files
#### <a name="Client.Rename">func</a> (\*Client) [Rename](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6652:6722#L318)
#### <a name="Client.Rename">func</a> (\*Client) [Rename](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6649:6719#L305)
``` go
func (c *Client) Rename(oldpath, newpath string, overwrite bool) error
```
Rename moves a file from A to B
#### <a name="Client.SetHeader">func</a> (\*Client) [SetHeader](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1235:1280#L67)
#### <a name="Client.SetHeader">func</a> (\*Client) [SetHeader](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1092:1137#L49)
``` go
func (c *Client) SetHeader(key, value string)
```
SetHeader lets us set arbitrary headers for a given client
#### <a name="Client.SetInterceptor">func</a> (\*Client) [SetInterceptor](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1387:1469#L72)
#### <a name="Client.SetInterceptor">func</a> (\*Client) [SetInterceptor](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1244:1326#L54)
``` go
func (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request))
```
SetInterceptor lets us set an arbitrary interceptor for a given client
#### <a name="Client.SetTimeout">func</a> (\*Client) [SetTimeout](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1571:1621#L77)
#### <a name="Client.SetJar">func</a> (\*Client) [SetJar](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1727:1770#L69)
``` go
func (c *Client) SetJar(jar http.CookieJar)
```
SetJar exposes the ability to set a cookie jar to the client.
#### <a name="Client.SetTimeout">func</a> (\*Client) [SetTimeout](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1428:1478#L59)
``` go
func (c *Client) SetTimeout(timeout time.Duration)
```
SetTimeout exposes the ability to set a time limit for requests
#### <a name="Client.SetTransport">func</a> (\*Client) [SetTransport](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1714:1772#L82)
#### <a name="Client.SetTransport">func</a> (\*Client) [SetTransport](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1571:1629#L64)
``` go
func (c *Client) SetTransport(transport http.RoundTripper)
```
SetTransport exposes the ability to define custom transports
#### <a name="Client.Stat">func</a> (\*Client) [Stat](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=4255:4310#L197)
#### <a name="Client.Stat">func</a> (\*Client) [Stat](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=4241:4296#L184)
``` go
func (c *Client) Stat(path string) (os.FileInfo, error)
```
Stat returns the file stats for a specified path
#### <a name="Client.Write">func</a> (\*Client) [Write](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9260:9335#L402)
#### <a name="Client.Write">func</a> (\*Client) [Write](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9272:9347#L389)
``` go
func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error)
```
Write writes data to a given path
#### <a name="Client.WriteStream">func</a> (\*Client) [WriteStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9759:9845#L432)
#### <a name="Client.WriteStream">func</a> (\*Client) [WriteStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9771:9857#L419)
``` go
func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error)
```
@@ -460,29 +636,35 @@ type DigestAuth struct {
```
DigestAuth structure holds our credentials
#### <a name="DigestAuth.Authorize">func</a> (\*DigestAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=577:654#L36)
#### <a name="DigestAuth.Authorize">func</a> (\*DigestAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=525:608#L26)
``` go
func (d *DigestAuth) Authorize(req *http.Request, method string, path string)
func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error
```
Authorize the current request
#### <a name="DigestAuth.Pass">func</a> (\*DigestAuth) [Pass](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=491:525#L31)
#### <a name="DigestAuth.Clone">func</a> (\*DigestAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1228:1270#L49)
``` go
func (d *DigestAuth) Pass() string
func (d *DigestAuth) Clone() Authenticator
```
Pass holds the DigestAuth password
Clone creates a copy of itself
#### <a name="DigestAuth.Type">func</a> (\*DigestAuth) [Type](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=299:333#L21)
#### <a name="DigestAuth.Close">func</a> (\*DigestAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1142:1176#L44)
``` go
func (d *DigestAuth) Type() string
func (d *DigestAuth) Close() error
```
Type identifies the DigestAuthenticator
Close cleans up all resources
#### <a name="DigestAuth.User">func</a> (\*DigestAuth) [User](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=398:432#L26)
#### <a name="DigestAuth.String">func</a> (\*DigestAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1466:1502#L58)
``` go
func (d *DigestAuth) User() string
func (d *DigestAuth) String() string
```
User holds the DigestAuth username
String toString
#### <a name="DigestAuth.Verify">func</a> (\*DigestAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=912:1010#L36)
``` go
func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)
```
Verify checks for authentication issues and may trigger a re-authentication
### <a name="File">type</a> [File](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=93:253#L10)
``` go
@@ -553,40 +735,46 @@ func (f File) Sys() interface{}
```
Sys ????
### <a name="NoAuth">type</a> [NoAuth](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=551:599#L37)
### <a name="PassportAuth">type</a> [PassportAuth](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=125:254#L12)
``` go
type NoAuth struct {
type PassportAuth struct {
// contains filtered or unexported fields
}
```
NoAuth structure holds our credentials
PassportAuth structure holds our credentials
#### <a name="NoAuth.Authorize">func</a> (\*NoAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=894:967#L58)
#### <a name="PassportAuth.Authorize">func</a> (\*PassportAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=690:775#L32)
``` go
func (n *NoAuth) Authorize(req *http.Request, method string, path string)
func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error
```
Authorize the current request
#### <a name="NoAuth.Pass">func</a> (\*NoAuth) [Pass](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=812:842#L53)
#### <a name="PassportAuth.Clone">func</a> (\*PassportAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1701:1745#L69)
``` go
func (n *NoAuth) Pass() string
func (p *PassportAuth) Clone() Authenticator
```
Pass returns the current password
Clone creates a Copy of itself
#### <a name="NoAuth.Type">func</a> (\*NoAuth) [Type](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=638:668#L43)
#### <a name="PassportAuth.Close">func</a> (\*PassportAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1613:1649#L64)
``` go
func (n *NoAuth) Type() string
func (p *PassportAuth) Close() error
```
Type identifies the authenticator
Close cleans up all resources
#### <a name="NoAuth.User">func</a> (\*NoAuth) [User](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=724:754#L48)
#### <a name="PassportAuth.String">func</a> (\*PassportAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=2048:2086#L83)
``` go
func (n *NoAuth) User() string
func (p *PassportAuth) String() string
```
User returns the current user
String toString
### <a name="StatusError">type</a> [StatusError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=114:153#L10)
#### <a name="PassportAuth.Verify">func</a> (\*PassportAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1075:1175#L46)
``` go
func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)
```
Verify verifies if the authentication is good
### <a name="StatusError">type</a> [StatusError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=499:538#L18)
``` go
type StatusError struct {
Status int
@@ -596,7 +784,7 @@ type StatusError struct {
StatusError implements error and wraps
an erroneous status code.
#### <a name="StatusError.Error">func</a> (StatusError) [Error](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=155:191#L14)
#### <a name="StatusError.Error">func</a> (StatusError) [Error](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=540:576#L22)
``` go
func (se StatusError) Error() string
```

409
vendor/github.com/studio-b12/gowebdav/auth.go generated vendored Normal file
View File

@@ -0,0 +1,409 @@
package gowebdav
import (
"bytes"
"errors"
"io"
"net/http"
"strings"
"sync"
)
// AuthFactory prototype function to create a new Authenticator
type AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error)
// Authorizer our Authenticator factory which creates an
// `Authenticator` per action/request.
type Authorizer interface {
// Creates a new Authenticator Shim per request.
// It may track request related states and perform payload buffering
// for authentication round trips.
// The underlying Authenticator will perform the real authentication.
NewAuthenticator(body io.Reader) (Authenticator, io.Reader)
// Registers a new Authenticator factory to a key.
AddAuthenticator(key string, fn AuthFactory)
}
// A Authenticator implements a specific way to authorize requests.
// Each request is bound to a separate Authenticator instance.
//
// The authentication flow itself is broken down into `Authorize`
// and `Verify` steps. The former method runs before, and the latter
// runs after the `Request` is submitted.
// This makes it easy to encapsulate and control complex
// authentication challenges.
//
// Some authentication flows causing authentication round trips,
// which can be archived by returning the `redo` of the Verify
// method. `True` restarts the authentication process for the
// current action: A new `Request` is spawned, which must be
// authorized, sent, and re-verified again, until the action
// is successfully submitted.
// The preferred way is to handle the authentication ping-pong
// within `Verify`, and then `redo` with fresh credentials.
//
// The result of the `Verify` method can also trigger an
// `Authenticator` change by returning the `ErrAuthChanged`
// as an error. Depending on the `Authorizer` this may trigger
// an `Authenticator` negotiation.
//
// Set the `XInhibitRedirect` header to '1' in the `Authorize`
// method to get control over request redirection.
// Attention! You must handle the incoming request yourself.
//
// To store a shared session state the `Clone` method **must**
// return a new instance, initialized with the shared state.
type Authenticator interface {
// Authorizes a request. Usually by adding some authorization headers.
Authorize(c *http.Client, rq *http.Request, path string) error
// Verifies the response if the authorization was successful.
// May trigger some round trips to pass the authentication.
// May also trigger a new Authenticator negotiation by returning `ErrAuthChenged`
Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)
// Creates a copy of the underlying Authenticator.
Clone() Authenticator
io.Closer
}
type authfactory struct {
key string
create AuthFactory
}
// authorizer structure holds our Authenticator create functions
type authorizer struct {
factories []authfactory
defAuthMux sync.Mutex
defAuth Authenticator
}
// preemptiveAuthorizer structure holds the preemptive Authenticator
type preemptiveAuthorizer struct {
auth Authenticator
}
// authShim structure that wraps the real Authenticator
type authShim struct {
factory AuthFactory
body io.Reader
auth Authenticator
}
// negoAuth structure holds the authenticators that are going to be negotiated
type negoAuth struct {
auths []Authenticator
setDefaultAuthenticator func(auth Authenticator)
}
// nullAuth initializes the whole authentication flow
type nullAuth struct{}
// noAuth structure to perform no authentication at all
type noAuth struct{}
// NewAutoAuth creates an auto Authenticator factory.
// It negotiates the default authentication method
// based on the order of the registered Authenticators
// and the remotely offered authentication methods.
// First In, First Out.
func NewAutoAuth(login string, secret string) Authorizer {
fmap := make([]authfactory, 0)
az := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}}
az.AddAuthenticator("basic", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {
return &BasicAuth{user: login, pw: secret}, nil
})
az.AddAuthenticator("digest", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {
return NewDigestAuth(login, secret, rs)
})
az.AddAuthenticator("passport1.4", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {
return NewPassportAuth(c, login, secret, rs.Request.URL.String(), &rs.Header)
})
return az
}
// NewEmptyAuth creates an empty Authenticator factory
// The order of adding the Authenticator matters.
// First In, First Out.
// It offers the `NewAutoAuth` features.
func NewEmptyAuth() Authorizer {
fmap := make([]authfactory, 0)
az := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}}
return az
}
// NewPreemptiveAuth creates a preemptive Authenticator
// The preemptive authorizer uses the provided Authenticator
// for every request regardless of any `Www-Authenticate` header.
//
// It may only have one authentication method,
// so calling `AddAuthenticator` **will panic**!
//
// Look out!! This offers the skinniest and slickest implementation
// without any synchronisation!!
// Still applicable with `BasicAuth` within go routines.
func NewPreemptiveAuth(auth Authenticator) Authorizer {
return &preemptiveAuthorizer{auth: auth}
}
// NewAuthenticator creates an Authenticator (Shim) per request
func (a *authorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) {
var retryBuf io.Reader = body
if body != nil {
// If the authorization fails, we will need to restart reading
// from the passed body stream.
// When body is seekable, use seek to reset the streams
// cursor to the start.
// Otherwise, copy the stream into a buffer while uploading
// and use the buffers content on retry.
if _, ok := retryBuf.(io.Seeker); ok {
body = io.NopCloser(body)
} else {
buff := &bytes.Buffer{}
retryBuf = buff
body = io.TeeReader(body, buff)
}
}
a.defAuthMux.Lock()
defAuth := a.defAuth.Clone()
a.defAuthMux.Unlock()
return &authShim{factory: a.factory, body: retryBuf, auth: defAuth}, body
}
// AddAuthenticator appends the AuthFactory to our factories.
// It converts the key to lower case and preserves the order.
func (a *authorizer) AddAuthenticator(key string, fn AuthFactory) {
key = strings.ToLower(key)
for _, f := range a.factories {
if f.key == key {
panic("Authenticator exists: " + key)
}
}
a.factories = append(a.factories, authfactory{key, fn})
}
// factory picks all valid Authenticators based on Www-Authenticate headers
func (a *authorizer) factory(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {
headers := rs.Header.Values("Www-Authenticate")
if len(headers) > 0 {
auths := make([]Authenticator, 0)
for _, f := range a.factories {
for _, header := range headers {
headerLower := strings.ToLower(header)
if strings.Contains(headerLower, f.key) {
rs.Header.Set("Www-Authenticate", header)
if auth, err = f.create(c, rs, path); err == nil {
auths = append(auths, auth)
break
}
}
}
}
switch len(auths) {
case 0:
return nil, NewPathError("NoAuthenticator", path, rs.StatusCode)
case 1:
auth = auths[0]
default:
auth = &negoAuth{auths: auths, setDefaultAuthenticator: a.setDefaultAuthenticator}
}
} else {
auth = &noAuth{}
}
a.setDefaultAuthenticator(auth)
return auth, nil
}
// setDefaultAuthenticator sets the default Authenticator
func (a *authorizer) setDefaultAuthenticator(auth Authenticator) {
a.defAuthMux.Lock()
a.defAuth.Close()
a.defAuth = auth
a.defAuthMux.Unlock()
}
// Authorize the current request
func (s *authShim) Authorize(c *http.Client, rq *http.Request, path string) error {
if err := s.auth.Authorize(c, rq, path); err != nil {
return err
}
body := s.body
rq.GetBody = func() (io.ReadCloser, error) {
if body != nil {
if sk, ok := body.(io.Seeker); ok {
if _, err := sk.Seek(0, io.SeekStart); err != nil {
return nil, err
}
}
return io.NopCloser(body), nil
}
return nil, nil
}
return nil
}
// Verify checks for authentication issues and may trigger a re-authentication.
// Catches AlgoChangedErr to update the current Authenticator
func (s *authShim) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
redo, err = s.auth.Verify(c, rs, path)
if err != nil && errors.Is(err, ErrAuthChanged) {
if auth, aerr := s.factory(c, rs, path); aerr == nil {
s.auth.Close()
s.auth = auth
return true, nil
} else {
return false, aerr
}
}
return
}
// Close closes all resources
func (s *authShim) Close() error {
s.auth.Close()
s.auth, s.factory = nil, nil
if s.body != nil {
if closer, ok := s.body.(io.Closer); ok {
return closer.Close()
}
}
return nil
}
// It's not intend to Clone the shim
// therefore it returns a noAuth instance
func (s *authShim) Clone() Authenticator {
return &noAuth{}
}
// String toString
func (s *authShim) String() string {
return "AuthShim"
}
// Authorize authorizes the current request with the top most Authorizer
func (n *negoAuth) Authorize(c *http.Client, rq *http.Request, path string) error {
if len(n.auths) == 0 {
return NewPathError("NoAuthenticator", path, 400)
}
return n.auths[0].Authorize(c, rq, path)
}
// Verify verifies the authentication and selects the next one based on the result
func (n *negoAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
if len(n.auths) == 0 {
return false, NewPathError("NoAuthenticator", path, 400)
}
redo, err = n.auths[0].Verify(c, rs, path)
if err != nil {
if len(n.auths) > 1 {
n.auths[0].Close()
n.auths = n.auths[1:]
return true, nil
}
} else if redo {
return
} else {
auth := n.auths[0]
n.auths = n.auths[1:]
n.setDefaultAuthenticator(auth)
return
}
return false, NewPathError("NoAuthenticator", path, rs.StatusCode)
}
// Close will close the underlying authenticators.
func (n *negoAuth) Close() error {
for _, a := range n.auths {
a.Close()
}
n.setDefaultAuthenticator = nil
return nil
}
// Clone clones the underlying authenticators.
func (n *negoAuth) Clone() Authenticator {
auths := make([]Authenticator, len(n.auths))
for i, e := range n.auths {
auths[i] = e.Clone()
}
return &negoAuth{auths: auths, setDefaultAuthenticator: n.setDefaultAuthenticator}
}
func (n *negoAuth) String() string {
return "NegoAuth"
}
// Authorize the current request
func (n *noAuth) Authorize(c *http.Client, rq *http.Request, path string) error {
return nil
}
// Verify checks for authentication issues and may trigger a re-authentication
func (n *noAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
if "" != rs.Header.Get("Www-Authenticate") {
err = ErrAuthChanged
}
return
}
// Close closes all resources
func (n *noAuth) Close() error {
return nil
}
// Clone creates a copy of itself
func (n *noAuth) Clone() Authenticator {
// no copy due to read only access
return n
}
// String toString
func (n *noAuth) String() string {
return "NoAuth"
}
// Authorize the current request
func (n *nullAuth) Authorize(c *http.Client, rq *http.Request, path string) error {
rq.Header.Set(XInhibitRedirect, "1")
return nil
}
// Verify checks for authentication issues and may trigger a re-authentication
func (n *nullAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
return true, ErrAuthChanged
}
// Close closes all resources
func (n *nullAuth) Close() error {
return nil
}
// Clone creates a copy of itself
func (n *nullAuth) Clone() Authenticator {
// no copy due to read only access
return n
}
// String toString
func (n *nullAuth) String() string {
return "NullAuth"
}
// NewAuthenticator creates an Authenticator (Shim) per request
func (b *preemptiveAuthorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) {
return b.auth.Clone(), body
}
// AddAuthenticator Will PANIC because it may only have a single authentication method
func (b *preemptiveAuthorizer) AddAuthenticator(key string, fn AuthFactory) {
panic("You're funny! A preemptive authorizer may only have a single authentication method")
}

View File

@@ -1,7 +1,7 @@
package gowebdav
import (
"encoding/base64"
"fmt"
"net/http"
)
@@ -11,24 +11,32 @@ type BasicAuth struct {
pw string
}
// Type identifies the BasicAuthenticator
func (b *BasicAuth) Type() string {
return "BasicAuth"
}
// User holds the BasicAuth username
func (b *BasicAuth) User() string {
return b.user
}
// Pass holds the BasicAuth password
func (b *BasicAuth) Pass() string {
return b.pw
}
// Authorize the current request
func (b *BasicAuth) Authorize(req *http.Request, method string, path string) {
a := b.user + ":" + b.pw
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(a))
req.Header.Set("Authorization", auth)
func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error {
rq.SetBasicAuth(b.user, b.pw)
return nil
}
// Verify verifies if the authentication
func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
if rs.StatusCode == 401 {
err = NewPathError("Authorize", path, rs.StatusCode)
}
return
}
// Close cleans up all resources
func (b *BasicAuth) Close() error {
return nil
}
// Clone creates a Copy of itself
func (b *BasicAuth) Clone() Authenticator {
// no copy due to read only access
return b
}
// String toString
func (b *BasicAuth) String() string {
return fmt.Sprintf("BasicAuth login: %s", b.user)
}

View File

@@ -9,58 +9,43 @@ import (
"net/url"
"os"
pathpkg "path"
"strconv"
"strings"
"sync"
"time"
)
const XInhibitRedirect = "X-Gowebdav-Inhibit-Redirect"
var defaultProps = []string{"displayname", "resourcetype", "getcontentlength", "getcontenttype", "getetag", "getlastmodified"}
// Client defines our structure
type Client struct {
root string
headers http.Header
interceptor func(method string, rq *http.Request)
c *http.Client
authMutex sync.Mutex
auth Authenticator
}
// Authenticator stub
type Authenticator interface {
Type() string
User() string
Pass() string
Authorize(*http.Request, string, string)
}
// NoAuth structure holds our credentials
type NoAuth struct {
user string
pw string
}
// Type identifies the authenticator
func (n *NoAuth) Type() string {
return "NoAuth"
}
// User returns the current user
func (n *NoAuth) User() string {
return n.user
}
// Pass returns the current password
func (n *NoAuth) Pass() string {
return n.pw
}
// Authorize the current request
func (n *NoAuth) Authorize(req *http.Request, method string, path string) {
auth Authorizer
}
// NewClient creates a new instance of client
func NewClient(uri, user, pw string) *Client {
return &Client{FixSlash(uri), make(http.Header), nil, &http.Client{}, sync.Mutex{}, &NoAuth{user, pw}}
return NewAuthClient(uri, NewAutoAuth(user, pw))
}
// NewAuthClient creates a new client instance with a custom Authorizer
func NewAuthClient(uri string, auth Authorizer) *Client {
c := &http.Client{
CheckRedirect: func(rq *http.Request, via []*http.Request) error {
if len(via) >= 10 {
return ErrTooManyRedirects
}
if via[0].Header.Get(XInhibitRedirect) != "" {
return http.ErrUseLastResponse
}
return nil
},
}
return &Client{root: FixSlash(uri), headers: make(http.Header), interceptor: nil, c: c, auth: auth}
}
// SetHeader lets us set arbitrary headers for a given client
@@ -83,6 +68,11 @@ func (c *Client) SetTransport(transport http.RoundTripper) {
c.c.Transport = transport
}
// SetJar exposes the ability to set a cookie jar to the client.
func (c *Client) SetJar(jar http.CookieJar) {
c.c.Jar = jar
}
// Connect connects to our dav server
func (c *Client) Connect() error {
rs, err := c.options("/")
@@ -96,29 +86,86 @@ func (c *Client) Connect() error {
}
if rs.StatusCode != 200 {
return newPathError("Connect", c.root, rs.StatusCode)
return NewPathError("Connect", c.root, rs.StatusCode)
}
return nil
}
type props struct {
Status string `xml:"DAV: status"`
Name string `xml:"DAV: prop>displayname,omitempty"`
Type xml.Name `xml:"DAV: prop>resourcetype>collection,omitempty"`
Size string `xml:"DAV: prop>getcontentlength,omitempty"`
ContentType string `xml:"DAV: prop>getcontenttype,omitempty"`
ETag string `xml:"DAV: prop>getetag,omitempty"`
Modified string `xml:"DAV: prop>getlastmodified,omitempty"`
type Props struct {
m map[xml.Name]interface{}
}
func (c *Props) GetString(key xml.Name) string {
return fmt.Sprintf("%v", c.m[key])
}
func (c *Props) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
c.m = make(map[xml.Name]interface{})
type flatProp struct {
XMLName xml.Name
Content string `xml:",innerxml"`
}
type flatProps struct {
Props []flatProp `xml:",any"`
}
parsedProps := flatProps{}
if err := d.DecodeElement(&parsedProps, &start); err != nil {
return err
}
for _, v := range parsedProps.Props {
c.m[v.XMLName] = v.Content
}
return nil
}
type propstat struct {
Status string `xml:"DAV: status"`
Props Props `xml:"DAV: prop"`
}
func (p *propstat) Type() string {
if p.Props.GetString(xml.Name{Space: "DAV:", Local: "resourcetype"}) == "" {
return "file"
}
return "collection"
}
func (p *propstat) Name() string {
return p.Props.GetString(xml.Name{Space: "DAV:", Local: "displayname"})
}
func (p *propstat) ContentType() string {
return p.Props.GetString(xml.Name{Space: "DAV:", Local: "getcontenttype"})
}
func (p *propstat) Size() int64 {
if n, e := strconv.ParseInt(p.Props.GetString(xml.Name{Space: "DAV:", Local: "getcontentlength"}), 10, 64); e == nil {
return n
}
return 0
}
func (p *propstat) ETag() string {
return p.Props.GetString(xml.Name{Space: "DAV:", Local: "getetag"})
}
func (p *propstat) Modified() time.Time {
if t, e := time.Parse(time.RFC1123, p.Props.GetString(xml.Name{Space: "DAV:", Local: "getlastmodified"})); e == nil {
return t
}
return time.Unix(0, 0)
}
type response struct {
Href string `xml:"DAV: href"`
Props []props `xml:"DAV: propstat"`
Href string `xml:"DAV: href"`
Propstats []propstat `xml:"DAV: propstat"`
}
func getProps(r *response, status string) *props {
for _, prop := range r.Props {
func getPropstat(r *response, status string) *propstat {
for _, prop := range r.Propstats {
if strings.Contains(prop.Status, status) {
return &prop
}
@@ -128,7 +175,16 @@ func getProps(r *response, status string) *props {
// ReadDir reads the contents of a remote directory
func (c *Client) ReadDir(path string) ([]os.FileInfo, error) {
path = FixSlashes(path)
return c.ReadDirWithProps(path, defaultProps)
}
// ReadDirWithProps reads the contents of the directory at the given path, along with the specified properties.
func (c *Client) ReadDirWithProps(path string, props []string) ([]os.FileInfo, error) {
propfindprops := ""
if len(props) > 0 {
propfindprops = `<d:prop><d:` + strings.Join(props, "/><d:") + `/></d:prop>`
}
files := make([]os.FileInfo, 0)
skipSelf := true
parse := func(resp interface{}) error {
@@ -136,113 +192,76 @@ func (c *Client) ReadDir(path string) ([]os.FileInfo, error) {
if skipSelf {
skipSelf = false
if p := getProps(r, "200"); p != nil && p.Type.Local == "collection" {
r.Props = nil
if p := getPropstat(r, "200"); p != nil && p.Type() == "collection" {
r.Propstats = nil
return nil
}
return newPathError("ReadDir", path, 405)
return NewPathError("ReadDir", path, 405)
}
if p := getProps(r, "200"); p != nil {
f := new(File)
if p := getPropstat(r, "200"); p != nil {
var name string
if ps, err := url.PathUnescape(r.Href); err == nil {
f.name = pathpkg.Base(ps)
name = pathpkg.Base(ps)
} else {
f.name = p.Name
name = p.Name()
}
f.path = path + f.name
f.modified = parseModified(&p.Modified)
f.etag = p.ETag
f.contentType = p.ContentType
if p.Type.Local == "collection" {
f.path += "/"
f.size = 0
f.isdir = true
} else {
f.size = parseInt64(&p.Size)
f.isdir = false
}
files = append(files, *f)
files = append(files, *newFile(path, name, p))
}
r.Props = nil
r.Propstats = nil
return nil
}
err := c.propfind(path, false,
`<d:propfind xmlns:d='DAV:'>
<d:prop>
<d:displayname/>
<d:resourcetype/>
<d:getcontentlength/>
<d:getcontenttype/>
<d:getetag/>
<d:getlastmodified/>
</d:prop>
</d:propfind>`,
`<d:propfind xmlns:d='DAV:'>`+propfindprops+`</d:propfind>`,
&response{},
parse)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
err = newPathErrorErr("ReadDir", path, err)
err = NewPathErrorErr("ReadDir", path, err)
}
}
return files, err
}
// Stat returns the file stats for a specified path
// Stat returns the file stats for a specified path with the default properties
func (c *Client) Stat(path string) (os.FileInfo, error) {
return c.StatWithProps(path, defaultProps)
}
// StatWithProps returns the FileInfo for the specified path along with the specified properties.
func (c *Client) StatWithProps(path string, props []string) (os.FileInfo, error) {
var f *File
parse := func(resp interface{}) error {
r := resp.(*response)
if p := getProps(r, "200"); p != nil && f == nil {
f = new(File)
f.name = p.Name
f.path = path
f.etag = p.ETag
f.contentType = p.ContentType
if p.Type.Local == "collection" {
if !strings.HasSuffix(f.path, "/") {
f.path += "/"
}
f.size = 0
f.modified = time.Unix(0, 0)
f.isdir = true
} else {
f.size = parseInt64(&p.Size)
f.modified = parseModified(&p.Modified)
f.isdir = false
}
if p := getPropstat(r, "200"); p != nil && f == nil {
f = newFile(".", path, p)
}
r.Props = nil
r.Propstats = nil
return nil
}
propXML := "<d:propfind xmlns:d='DAV:'><d:prop>"
for _, prop := range props {
propXML += "<d:" + prop + "/>"
}
propXML += "</d:prop></d:propfind>"
err := c.propfind(path, true,
`<d:propfind xmlns:d='DAV:'>
<d:prop>
<d:displayname/>
<d:resourcetype/>
<d:getcontentlength/>
<d:getcontenttype/>
<d:getetag/>
<d:getlastmodified/>
</d:prop>
</d:propfind>`,
propXML,
&response{},
parse)
if err != nil {
if _, ok := err.(*os.PathError); !ok {
err = newPathErrorErr("ReadDir", path, err)
return nil, NewPathErrorErr("ReadDir", path, err)
}
return nil, err
}
return f, err
return *f, err
}
// Remove removes a remote file
@@ -254,7 +273,7 @@ func (c *Client) Remove(path string) error {
func (c *Client) RemoveAll(path string) error {
rs, err := c.req("DELETE", path, nil, nil)
if err != nil {
return newPathError("Remove", path, 400)
return NewPathError("Remove", path, 400)
}
err = rs.Body.Close()
if err != nil {
@@ -265,7 +284,7 @@ func (c *Client) RemoveAll(path string) error {
return nil
}
return newPathError("Remove", path, rs.StatusCode)
return NewPathError("Remove", path, rs.StatusCode)
}
// Mkdir makes a directory
@@ -279,7 +298,7 @@ func (c *Client) Mkdir(path string, _ os.FileMode) (err error) {
return nil
}
return newPathError("Mkdir", path, status)
return NewPathError("Mkdir", path, status)
}
// MkdirAll like mkdir -p, but for webdav
@@ -305,13 +324,13 @@ func (c *Client) MkdirAll(path string, _ os.FileMode) (err error) {
return
}
if status != 201 {
return newPathError("MkdirAll", sub, status)
return NewPathError("MkdirAll", sub, status)
}
}
return nil
}
return newPathError("MkdirAll", path, status)
return NewPathError("MkdirAll", path, status)
}
// Rename moves a file from A to B
@@ -346,7 +365,7 @@ func (c *Client) Read(path string) ([]byte, error) {
func (c *Client) ReadStream(path string) (io.ReadCloser, error) {
rs, err := c.req("GET", path, nil, nil)
if err != nil {
return nil, newPathErrorErr("ReadStream", path, err)
return nil, NewPathErrorErr("ReadStream", path, err)
}
if rs.StatusCode == 200 {
@@ -354,7 +373,7 @@ func (c *Client) ReadStream(path string) (io.ReadCloser, error) {
}
rs.Body.Close()
return nil, newPathError("ReadStream", path, rs.StatusCode)
return nil, NewPathError("ReadStream", path, rs.StatusCode)
}
// ReadStreamRange reads the stream representing a subset of bytes for a given path,
@@ -374,7 +393,7 @@ func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadClos
}
})
if err != nil {
return nil, newPathErrorErr("ReadStreamRange", path, err)
return nil, NewPathErrorErr("ReadStreamRange", path, err)
}
if rs.StatusCode == http.StatusPartialContent {
@@ -387,15 +406,15 @@ func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadClos
if rs.StatusCode == 200 {
// discard first 'offset' bytes.
if _, err := io.Copy(io.Discard, io.LimitReader(rs.Body, offset)); err != nil {
return nil, newPathErrorErr("ReadStreamRange", path, err)
return nil, NewPathErrorErr("ReadStreamRange", path, err)
}
// return a io.ReadCloser that is limited to `length` bytes.
return &limitedReadCloser{rs.Body, int(length)}, nil
return &limitedReadCloser{rc: rs.Body, remaining: int(length)}, nil
}
rs.Body.Close()
return nil, newPathError("ReadStream", path, rs.StatusCode)
return nil, NewPathError("ReadStream", path, rs.StatusCode)
}
// Write writes data to a given path
@@ -425,7 +444,7 @@ func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error) {
}
}
return newPathError("Write", path, s)
return NewPathError("Write", path, s)
}
// WriteStream writes a stream
@@ -446,6 +465,6 @@ func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err
return nil
default:
return newPathError("WriteStream", path, s)
return NewPathError("WriteStream", path, s)
}
}

View File

@@ -17,28 +17,46 @@ type DigestAuth struct {
digestParts map[string]string
}
// Type identifies the DigestAuthenticator
func (d *DigestAuth) Type() string {
return "DigestAuth"
}
// User holds the DigestAuth username
func (d *DigestAuth) User() string {
return d.user
}
// Pass holds the DigestAuth password
func (d *DigestAuth) Pass() string {
return d.pw
// NewDigestAuth creates a new instance of our Digest Authenticator
func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error) {
return &DigestAuth{user: login, pw: secret, digestParts: digestParts(rs)}, nil
}
// Authorize the current request
func (d *DigestAuth) Authorize(req *http.Request, method string, path string) {
func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error {
d.digestParts["uri"] = path
d.digestParts["method"] = method
d.digestParts["method"] = rq.Method
d.digestParts["username"] = d.user
d.digestParts["password"] = d.pw
req.Header.Set("Authorization", getDigestAuthorization(d.digestParts))
rq.Header.Set("Authorization", getDigestAuthorization(d.digestParts))
return nil
}
// Verify checks for authentication issues and may trigger a re-authentication
func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
if rs.StatusCode == 401 {
err = NewPathError("Authorize", path, rs.StatusCode)
}
return
}
// Close cleans up all resources
func (d *DigestAuth) Close() error {
return nil
}
// Clone creates a copy of itself
func (d *DigestAuth) Clone() Authenticator {
parts := make(map[string]string, len(d.digestParts))
for k, v := range d.digestParts {
parts[k] = v
}
return &DigestAuth{user: d.user, pw: d.pw, digestParts: parts}
}
// String toString
func (d *DigestAuth) String() string {
return fmt.Sprintf("DigestAuth login: %s", d.user)
}
func digestParts(resp *http.Response) map[string]string {

View File

@@ -1,10 +1,18 @@
package gowebdav
import (
"errors"
"fmt"
"os"
)
// ErrAuthChanged must be returned from the Verify method as an error
// to trigger a re-authentication / negotiation with a new authenticator.
var ErrAuthChanged = errors.New("authentication failed, change algorithm")
// ErrTooManyRedirects will be used as return error if a request exceeds 10 redirects.
var ErrTooManyRedirects = errors.New("stopped after 10 redirects")
// StatusError implements error and wraps
// an erroneous status code.
type StatusError struct {
@@ -32,7 +40,7 @@ func IsErrNotFound(err error) bool {
return IsErrCode(err, 404)
}
func newPathError(op string, path string, statusCode int) error {
func NewPathError(op string, path string, statusCode int) error {
return &os.PathError{
Op: op,
Path: path,
@@ -40,7 +48,7 @@ func newPathError(op string, path string, statusCode int) error {
}
}
func newPathErrorErr(op string, path string, err error) error {
func NewPathErrorErr(op string, path string, err error) error {
return &os.PathError{
Op: op,
Path: path,

View File

@@ -3,6 +3,7 @@ package gowebdav
import (
"fmt"
"os"
"path/filepath"
"time"
)
@@ -15,6 +16,31 @@ type File struct {
modified time.Time
etag string
isdir bool
props Props
}
func newFile(path, name string, p *propstat) *File {
f := &File{
props: p.Props,
}
path = FixSlashes(path)
f.name = name
f.path = filepath.Clean(filepath.Join(path, f.name))
f.modified = p.Modified()
f.etag = p.ETag()
f.contentType = p.ContentType()
f.props = p.Props
if p.Type() == "collection" {
f.path = filepath.Clean(f.path + "/")
f.size = 0
f.isdir = true
} else {
f.size = p.Size()
f.isdir = false
}
return f
}
// Path returns the full path of a file
@@ -64,7 +90,7 @@ func (f File) IsDir() bool {
// Sys ????
func (f File) Sys() interface{} {
return nil
return f.props
}
// String lets us see file information

181
vendor/github.com/studio-b12/gowebdav/passportAuth.go generated vendored Normal file
View File

@@ -0,0 +1,181 @@
package gowebdav
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
)
// PassportAuth structure holds our credentials
type PassportAuth struct {
user string
pw string
cookies []http.Cookie
inhibitRedirect bool
}
// constructor for PassportAuth creates a new PassportAuth object and
// automatically authenticates against the given partnerURL
func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error) {
p := &PassportAuth{
user: user,
pw: pw,
inhibitRedirect: true,
}
err := p.genCookies(c, partnerURL, header)
return p, err
}
// Authorize the current request
func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error {
// prevent redirects to detect subsequent authentication requests
if p.inhibitRedirect {
rq.Header.Set(XInhibitRedirect, "1")
} else {
p.inhibitRedirect = true
}
for _, cookie := range p.cookies {
rq.AddCookie(&cookie)
}
return nil
}
// Verify verifies if the authentication is good
func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {
switch rs.StatusCode {
case 301, 302, 307, 308:
redo = true
if rs.Header.Get("Www-Authenticate") != "" {
// re-authentication required as we are redirected to the login page
err = p.genCookies(c, rs.Request.URL.String(), &rs.Header)
} else {
// just a redirect, follow it
p.inhibitRedirect = false
}
case 401:
err = NewPathError("Authorize", path, rs.StatusCode)
}
return
}
// Close cleans up all resources
func (p *PassportAuth) Close() error {
return nil
}
// Clone creates a Copy of itself
func (p *PassportAuth) Clone() Authenticator {
// create a copy to allow independent cookie updates
clonedCookies := make([]http.Cookie, len(p.cookies))
copy(clonedCookies, p.cookies)
return &PassportAuth{
user: p.user,
pw: p.pw,
cookies: clonedCookies,
inhibitRedirect: true,
}
}
// String toString
func (p *PassportAuth) String() string {
return fmt.Sprintf("PassportAuth login: %s", p.user)
}
func (p *PassportAuth) genCookies(c *http.Client, partnerUrl string, header *http.Header) error {
// For more details refer to:
// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pass/2c80637d-438c-4d4b-adc5-903170a779f3
// Skipping step 1 and 2 as we already have the partner server challenge
baseAuthenticationServer := header.Get("Location")
baseAuthenticationServerURL, err := url.Parse(baseAuthenticationServer)
if err != nil {
return err
}
// Skipping step 3 and 4 as we already know that we need and have the user's credentials
// Step 5 (Sign-in request)
authenticationServerUrl := url.URL{
Scheme: baseAuthenticationServerURL.Scheme,
Host: baseAuthenticationServerURL.Host,
Path: "/login2.srf",
}
partnerServerChallenge := strings.Split(header.Get("Www-Authenticate"), " ")[1]
req := http.Request{
Method: "GET",
URL: &authenticationServerUrl,
Header: http.Header{
"Authorization": []string{"Passport1.4 sign-in=" + url.QueryEscape(p.user) + ",pwd=" + url.QueryEscape(p.pw) + ",OrgVerb=GET,OrgUrl=" + partnerUrl + "," + partnerServerChallenge},
},
}
rs, err := c.Do(&req)
if err != nil {
return err
}
io.Copy(io.Discard, rs.Body)
rs.Body.Close()
if rs.StatusCode != 200 {
return NewPathError("Authorize", "/", rs.StatusCode)
}
// Step 6 (Token Response from Authentication Server)
tokenResponseHeader := rs.Header.Get("Authentication-Info")
if tokenResponseHeader == "" {
return NewPathError("Authorize", "/", 401)
}
tokenResponseHeaderList := strings.Split(tokenResponseHeader, ",")
token := ""
for _, tokenResponseHeader := range tokenResponseHeaderList {
if strings.HasPrefix(tokenResponseHeader, "from-PP='") {
token = tokenResponseHeader
break
}
}
if token == "" {
return NewPathError("Authorize", "/", 401)
}
// Step 7 (First Authentication Request to Partner Server)
origUrl, err := url.Parse(partnerUrl)
if err != nil {
return err
}
req = http.Request{
Method: "GET",
URL: origUrl,
Header: http.Header{
"Authorization": []string{"Passport1.4 " + token},
},
}
rs, err = c.Do(&req)
if err != nil {
return err
}
io.Copy(io.Discard, rs.Body)
rs.Body.Close()
if rs.StatusCode != 200 && rs.StatusCode != 302 {
return NewPathError("Authorize", "/", rs.StatusCode)
}
// Step 8 (Set Token Message from Partner Server)
cookies := rs.Header.Values("Set-Cookie")
p.cookies = make([]http.Cookie, len(cookies))
for i, cookie := range cookies {
cookieParts := strings.Split(cookie, ";")
cookieName := strings.Split(cookieParts[0], "=")[0]
cookieValue := strings.Split(cookieParts[0], "=")[1]
p.cookies[i] = http.Cookie{
Name: cookieName,
Value: cookieValue,
}
}
return nil
}

View File

@@ -1,7 +1,6 @@
package gowebdav
import (
"bytes"
"io"
"log"
"net/http"
@@ -9,83 +8,52 @@ import (
"strings"
)
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (rs *http.Response, err error) {
var redo bool
var r *http.Request
var retryBuf io.Reader
var uri = PathEscape(Join(c.root, path))
auth, body := c.auth.NewAuthenticator(body)
defer auth.Close()
if body != nil {
// If the authorization fails, we will need to restart reading
// from the passed body stream.
// When body is seekable, use seek to reset the streams
// cursor to the start.
// Otherwise, copy the stream into a buffer while uploading
// and use the buffers content on retry.
if sk, ok := body.(io.Seeker); ok {
if _, err = sk.Seek(0, io.SeekStart); err != nil {
return
for { // TODO auth.continue() strategy(true|n times|until)?
if r, err = http.NewRequest(method, uri, body); err != nil {
return
}
for k, vals := range c.headers {
for _, v := range vals {
r.Header.Add(k, v)
}
retryBuf = body
} else {
buff := &bytes.Buffer{}
retryBuf = buff
body = io.TeeReader(body, buff)
}
r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), body)
} else {
r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), nil)
}
if err != nil {
return nil, err
}
for k, vals := range c.headers {
for _, v := range vals {
r.Header.Add(k, v)
}
}
// make sure we read 'c.auth' only once since it will be substituted below
// and that is unsafe to do when multiple goroutines are running at the same time.
c.authMutex.Lock()
auth := c.auth
c.authMutex.Unlock()
auth.Authorize(r, method, path)
if intercept != nil {
intercept(r)
}
if c.interceptor != nil {
c.interceptor(method, r)
}
rs, err := c.c.Do(r)
if err != nil {
return nil, err
}
if rs.StatusCode == 401 && auth.Type() == "NoAuth" {
wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate"))
if strings.Index(wwwAuthenticateHeader, "digest") > -1 {
c.authMutex.Lock()
c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)}
c.authMutex.Unlock()
} else if strings.Index(wwwAuthenticateHeader, "basic") > -1 {
c.authMutex.Lock()
c.auth = &BasicAuth{auth.User(), auth.Pass()}
c.authMutex.Unlock()
} else {
return rs, newPathError("Authorize", c.root, rs.StatusCode)
}
// retryBuf will be nil if body was nil initially so no check
// for body == nil is required here.
return c.req(method, path, retryBuf, intercept)
} else if rs.StatusCode == 401 {
return rs, newPathError("Authorize", c.root, rs.StatusCode)
if err = auth.Authorize(c.c, r, path); err != nil {
return
}
if intercept != nil {
intercept(r)
}
if c.interceptor != nil {
c.interceptor(method, r)
}
if rs, err = c.c.Do(r); err != nil {
return
}
if redo, err = auth.Verify(c.c, rs, path); err != nil {
rs.Body.Close()
return nil, err
}
if redo {
rs.Body.Close()
if body, err = r.GetBody(); err != nil {
return nil, err
}
continue
}
break
}
return rs, err
@@ -131,7 +99,7 @@ func (c *Client) propfind(path string, self bool, body string, resp interface{},
defer rs.Body.Close()
if rs.StatusCode != 207 {
return newPathError("PROPFIND", path, rs.StatusCode)
return NewPathError("PROPFIND", path, rs.StatusCode)
}
return parseXML(rs.Body, resp, parse)
@@ -189,7 +157,7 @@ func (c *Client) copymove(method string, oldpath string, newpath string, overwri
return c.copymove(method, oldpath, newpath, overwrite)
}
return newPathError(method, oldpath, s)
return NewPathError(method, oldpath, s)
}
func (c *Client) put(path string, stream io.Reader) (status int, err error) {

View File

@@ -5,9 +5,7 @@ import (
"encoding/xml"
"io"
"net/url"
"strconv"
"strings"
"time"
)
// PathEscape escapes all segments of a given path
@@ -49,27 +47,6 @@ func String(r io.Reader) string {
return buf.String()
}
func parseUint(s *string) uint {
if n, e := strconv.ParseUint(*s, 10, 32); e == nil {
return uint(n)
}
return 0
}
func parseInt64(s *string) int64 {
if n, e := strconv.ParseInt(*s, 10, 64); e == nil {
return n
}
return 0
}
func parseModified(s *string) time.Time {
if t, e := time.Parse(time.RFC1123, *s); e == nil {
return t
}
return time.Unix(0, 0)
}
func parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) error) error {
decoder := xml.NewDecoder(data)
for t, _ := decoder.Token(); t != nil; t, _ = decoder.Token() {

5
vendor/modules.txt vendored
View File

@@ -362,7 +362,7 @@ github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1
github.com/cs3org/go-cs3apis/cs3/storage/registry/v1beta1
github.com/cs3org/go-cs3apis/cs3/tx/v1beta1
github.com/cs3org/go-cs3apis/cs3/types/v1beta1
# github.com/cs3org/reva/v2 v2.17.0
# github.com/cs3org/reva/v2 v2.17.1-0.20231214082636-a8467337208a
## explicit; go 1.21
github.com/cs3org/reva/v2/cmd/revad/internal/grace
github.com/cs3org/reva/v2/cmd/revad/runtime
@@ -1721,7 +1721,7 @@ github.com/stretchr/objx
github.com/stretchr/testify/assert
github.com/stretchr/testify/mock
github.com/stretchr/testify/require
# github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423
# github.com/studio-b12/gowebdav v0.0.0-20221015232716-17255f2e7423 => github.com/aduffeck/gowebdav v0.0.0-20231123085457-ff658b6ea159
## explicit; go 1.17
github.com/studio-b12/gowebdav
# github.com/tchap/go-patricia/v2 v2.3.1
@@ -2298,3 +2298,4 @@ stash.kopano.io/kgol/oidc-go
## explicit; go 1.13
stash.kopano.io/kgol/rndm
# github.com/go-micro/plugins/v4/store/nats-js-kv => github.com/kobergj/plugins/v4/store/nats-js-kv v0.0.0-20231207143248-4d424e3ae348
# github.com/studio-b12/gowebdav => github.com/aduffeck/gowebdav v0.0.0-20231123085457-ff658b6ea159