mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-24 05:30:06 -06:00
initial user provisioning
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
@@ -5,11 +5,12 @@ import (
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/response"
|
||||
)
|
||||
|
||||
// GetConfig renders the ocs config endpoint
|
||||
func (o Ocs) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, DataRender(&data.ConfigData{
|
||||
render.Render(w, r, response.DataRender(&data.ConfigData{
|
||||
Version: "1.7", // TODO get from env
|
||||
Website: "ocis", // TODO get from env
|
||||
Host: "", // TODO get from FRONTEND config
|
||||
|
||||
@@ -42,9 +42,10 @@ type Capabilities struct {
|
||||
|
||||
// CapabilitiesCore holds webdav config
|
||||
type CapabilitiesCore struct {
|
||||
PollInterval int `json:"pollinterval" xml:"pollinterval" mapstructure:"poll_interval"`
|
||||
WebdavRoot string `json:"webdav-root,omitempty" xml:"webdav-root,omitempty" mapstructure:"webdav_root"`
|
||||
Status *Status `json:"status" xml:"status"`
|
||||
PollInterval int `json:"pollinterval" xml:"pollinterval" mapstructure:"poll_interval"`
|
||||
WebdavRoot string `json:"webdav-root,omitempty" xml:"webdav-root,omitempty" mapstructure:"webdav_root"`
|
||||
Status *Status `json:"status" xml:"status" mapstructure:"status"`
|
||||
SupportURLSigning ocsBool `json:"support-url-signing,omitempty" xml:"support-url-signing,omitempty" mapstructure:"support-url-signing"`
|
||||
}
|
||||
|
||||
// Status holds basic status information
|
||||
|
||||
28
pkg/service/v0/data/meta.go
Normal file
28
pkg/service/v0/data/meta.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package data
|
||||
|
||||
// Meta holds response metadata
|
||||
type Meta struct {
|
||||
Status string `json:"status" xml:"status"`
|
||||
StatusCode int `json:"statuscode" xml:"statuscode"`
|
||||
Message string `json:"message" xml:"message"`
|
||||
TotalItems string `json:"totalitems,omitempty" xml:"totalitems,omitempty"`
|
||||
ItemsPerPage string `json:"itemsperpage,omitempty" xml:"itemsperpage,omitempty"`
|
||||
}
|
||||
|
||||
// MetaOK is the default ok response
|
||||
var MetaOK = Meta{Status: "ok", StatusCode: 100, Message: "OK"}
|
||||
|
||||
// MetaBadRequest is used for unknown errors
|
||||
var MetaBadRequest = Meta{Status: "error", StatusCode: 400, Message: "Bad Request"}
|
||||
|
||||
// MetaServerError is returned on server errors
|
||||
var MetaServerError = Meta{Status: "error", StatusCode: 996, Message: "Server Error"}
|
||||
|
||||
// MetaUnauthorized is returned on unauthorized requests
|
||||
var MetaUnauthorized = Meta{Status: "error", StatusCode: 997, Message: "Unauthorised"}
|
||||
|
||||
// MetaNotFound is returned when trying to access not existing resources
|
||||
var MetaNotFound = Meta{Status: "error", StatusCode: 998, Message: "Not Found"}
|
||||
|
||||
// MetaUnknownError is used for unknown errors
|
||||
var MetaUnknownError = Meta{Status: "error", StatusCode: 999, Message: "Unknown Error"}
|
||||
@@ -1,4 +1,4 @@
|
||||
package svc
|
||||
package response
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
|
||||
)
|
||||
|
||||
// Response is the top level response structure
|
||||
@@ -23,7 +24,7 @@ var (
|
||||
// Payload combines response metadata and data
|
||||
type Payload struct {
|
||||
XMLName struct{} `json:"-" xml:"ocs"`
|
||||
Meta Meta `json:"meta" xml:"meta"`
|
||||
Meta data.Meta `json:"meta" xml:"meta"`
|
||||
Data interface{} `json:"data,omitempty" xml:"data,omitempty"`
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ func (p *Payload) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
// DataRender creates an OK Payload for the given data
|
||||
func DataRender(d interface{}) render.Renderer {
|
||||
return &Payload{
|
||||
Meta: MetaOK,
|
||||
Meta: data.MetaOK,
|
||||
Data: d,
|
||||
}
|
||||
}
|
||||
@@ -92,12 +93,12 @@ func DataRender(d interface{}) render.Renderer {
|
||||
// The httpcode will be determined using the API version stored in the context
|
||||
func ErrRender(c int, m string) render.Renderer {
|
||||
return &Payload{
|
||||
Meta: Meta{Status: "error", StatusCode: c, Message: m},
|
||||
Meta: data.Meta{Status: "error", StatusCode: c, Message: m},
|
||||
}
|
||||
}
|
||||
|
||||
func statusCodeMapper(version string) func(Meta) int {
|
||||
var mapper func(Meta) int
|
||||
func statusCodeMapper(version string) func(data.Meta) int {
|
||||
var mapper func(data.Meta) int
|
||||
switch version {
|
||||
case ocsVersion1:
|
||||
mapper = OcsV1StatusCodes
|
||||
@@ -1,4 +1,4 @@
|
||||
package svc
|
||||
package response
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
|
||||
)
|
||||
|
||||
type key int
|
||||
@@ -20,49 +21,31 @@ var (
|
||||
defaultStatusCodeMapper = OcsV2StatusCodes
|
||||
)
|
||||
|
||||
// Meta holds response metadata
|
||||
type Meta struct {
|
||||
Status string `json:"status" xml:"status"`
|
||||
StatusCode int `json:"statuscode" xml:"statuscode"`
|
||||
Message string `json:"message" xml:"message"`
|
||||
TotalItems string `json:"totalitems,omitempty" xml:"totalitems,omitempty"`
|
||||
ItemsPerPage string `json:"itemsperpage,omitempty" xml:"itemsperpage,omitempty"`
|
||||
// APIVersion retrieves the api version from the context.
|
||||
func APIVersion(ctx context.Context) string {
|
||||
value := ctx.Value(apiVersionKey)
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// MetaOK is the default ok response
|
||||
var MetaOK = Meta{Status: "ok", StatusCode: 100, Message: "OK"}
|
||||
|
||||
// MetaBadRequest is used for unknown errors
|
||||
var MetaBadRequest = Meta{Status: "error", StatusCode: 400, Message: "Bad Request"}
|
||||
|
||||
// MetaServerError is returned on server errors
|
||||
var MetaServerError = Meta{Status: "error", StatusCode: 996, Message: "Server Error"}
|
||||
|
||||
// MetaUnauthorized is returned on unauthorized requests
|
||||
var MetaUnauthorized = Meta{Status: "error", StatusCode: 997, Message: "Unauthorised"}
|
||||
|
||||
// MetaNotFound is returned when trying to access not existing resources
|
||||
var MetaNotFound = Meta{Status: "error", StatusCode: 998, Message: "Not Found"}
|
||||
|
||||
// MetaUnknownError is used for unknown errors
|
||||
var MetaUnknownError = Meta{Status: "error", StatusCode: 999, Message: "Unknown Error"}
|
||||
|
||||
// OcsV1StatusCodes returns the http status codes for the OCS API v1.
|
||||
func OcsV1StatusCodes(meta Meta) int {
|
||||
func OcsV1StatusCodes(meta data.Meta) int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
// OcsV2StatusCodes maps the OCS codes to http status codes for the ocs API v2.
|
||||
func OcsV2StatusCodes(meta Meta) int {
|
||||
func OcsV2StatusCodes(meta data.Meta) int {
|
||||
sc := meta.StatusCode
|
||||
switch sc {
|
||||
case MetaNotFound.StatusCode:
|
||||
case data.MetaNotFound.StatusCode:
|
||||
return http.StatusNotFound
|
||||
case MetaUnknownError.StatusCode:
|
||||
case data.MetaUnknownError.StatusCode:
|
||||
fallthrough
|
||||
case MetaServerError.StatusCode:
|
||||
case data.MetaServerError.StatusCode:
|
||||
return http.StatusInternalServerError
|
||||
case MetaUnauthorized.StatusCode:
|
||||
case data.MetaUnauthorized.StatusCode:
|
||||
return http.StatusUnauthorized
|
||||
case 100:
|
||||
meta.StatusCode = http.StatusOK
|
||||
@@ -82,23 +65,14 @@ func OcsV2StatusCodes(meta Meta) int {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
// APIVersion retrieves the api version from the context.
|
||||
func APIVersion(ctx context.Context) string {
|
||||
value := ctx.Value(apiVersionKey)
|
||||
if value != nil {
|
||||
return value.(string)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// VersionCtx middleware is used to determine the response mapper from
|
||||
// the URL parameters passed through as the request. In case
|
||||
// the Version is unknown, we stop here and return a 404.
|
||||
func (g Ocs) VersionCtx(next http.Handler) http.Handler {
|
||||
func VersionCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
version := chi.URLParam(r, "version")
|
||||
if version == "" {
|
||||
render.Render(w, r, ErrRender(MetaBadRequest.StatusCode, "unknown ocs api version"))
|
||||
render.Render(w, r, ErrRender(data.MetaBadRequest.StatusCode, "unknown ocs api version"))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Ocs-Api-Version", version)
|
||||
@@ -1,22 +1,17 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/go-chi/render"
|
||||
|
||||
"github.com/micro/go-micro/v2/client/grpc"
|
||||
merrors "github.com/micro/go-micro/v2/errors"
|
||||
"github.com/owncloud/ocis-ocs/pkg/config"
|
||||
ocsm "github.com/owncloud/ocis-ocs/pkg/middleware"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/response"
|
||||
"github.com/owncloud/ocis-pkg/v2/log"
|
||||
storepb "github.com/owncloud/ocis-store/pkg/proto/v0"
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
@@ -47,7 +42,7 @@ func NewService(opts ...Option) Service {
|
||||
))
|
||||
r.Use(ocsm.OCSFormatCtx) // updates request Accept header according to format=(json|xml) query parameter
|
||||
r.Route("/v{version:(1|2)}.php", func(r chi.Router) {
|
||||
r.Use(svc.VersionCtx) // stores version in context
|
||||
r.Use(response.VersionCtx) // stores version in context
|
||||
r.Route("/apps/files_sharing/api/v1", func(r chi.Router) {})
|
||||
r.Route("/apps/notifications/api/v1", func(r chi.Router) {})
|
||||
r.Route("/cloud", func(r chi.Router) {
|
||||
@@ -58,6 +53,7 @@ func NewService(opts ...Option) Service {
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", svc.ListUsers)
|
||||
r.Get("/{userid}", svc.GetUser)
|
||||
})
|
||||
})
|
||||
r.Route("/config", func(r chi.Router) {
|
||||
@@ -83,95 +79,5 @@ func (o Ocs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// NotFound uses ErrRender to always return a proper OCS payload
|
||||
func (o Ocs) NotFound(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, ErrRender(MetaUnknownError.StatusCode, "please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services"))
|
||||
}
|
||||
|
||||
// GetUser returns the currently logged in user
|
||||
func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
render.Render(w, r, ErrRender(MetaBadRequest.StatusCode, "missing user in context"))
|
||||
return
|
||||
}
|
||||
|
||||
render.Render(w, r, DataRender(&data.User{
|
||||
ID: u.Username, // TODO userid vs username! implications for clients if we return the userid here? -> implement graph ASAP?
|
||||
DisplayName: u.DisplayName,
|
||||
Email: u.Mail,
|
||||
}))
|
||||
}
|
||||
|
||||
// GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist
|
||||
// The signing key is part of the user settings and is used by the proxy to authenticate requests
|
||||
// Currently, the username is used as the OC-Credential
|
||||
func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) {
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
o.logger.Error().Msg("missing user in context")
|
||||
render.Render(w, r, ErrRender(MetaBadRequest.StatusCode, "missing user in context"))
|
||||
return
|
||||
}
|
||||
c := storepb.NewStoreService("com.owncloud.api.store", grpc.NewClient())
|
||||
res, err := c.Read(r.Context(), &storepb.ReadRequest{
|
||||
Options: &storepb.ReadOptions{
|
||||
Database: "proxy",
|
||||
Table: "signing-keys",
|
||||
},
|
||||
Key: u.Username,
|
||||
})
|
||||
if err == nil && len(res.Records) > 0 {
|
||||
render.Render(w, r, DataRender(&data.SigningKey{
|
||||
User: u.Username,
|
||||
SigningKey: string(res.Records[0].Value),
|
||||
}))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
e := merrors.Parse(err.Error())
|
||||
if e.Code == http.StatusNotFound {
|
||||
o.logger.Debug().Str("username", u.Username).Msg("signing key not found")
|
||||
// not found is ok, so we can continue and generate the key on the fly
|
||||
} else {
|
||||
o.logger.Err(err).Msg("error reading from store")
|
||||
render.Render(w, r, ErrRender(MetaServerError.StatusCode, "error reading from store"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// try creating it
|
||||
key := make([]byte, 64)
|
||||
_, err = rand.Read(key[:])
|
||||
if err != nil {
|
||||
o.logger.Error().Err(err).Msg("could not generate signing key")
|
||||
render.Render(w, r, ErrRender(MetaServerError.StatusCode, "could not generate signing key"))
|
||||
return
|
||||
}
|
||||
signingKey := hex.EncodeToString(key)
|
||||
|
||||
_, err = c.Write(r.Context(), &storepb.WriteRequest{
|
||||
Options: &storepb.WriteOptions{
|
||||
Database: "proxy",
|
||||
Table: "signing-keys",
|
||||
},
|
||||
Record: &storepb.Record{
|
||||
Key: u.Username,
|
||||
Value: []byte(signingKey),
|
||||
// TODO Expiry?
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
o.logger.Error().Err(err).Msg("error writing key")
|
||||
render.Render(w, r, ErrRender(MetaServerError.StatusCode, "could not persist signing key"))
|
||||
return
|
||||
}
|
||||
|
||||
render.Render(w, r, DataRender(&data.SigningKey{
|
||||
User: u.Username,
|
||||
SigningKey: signingKey,
|
||||
}))
|
||||
}
|
||||
|
||||
// ListUsers lists the users
|
||||
func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, ErrRender(MetaUnknownError.StatusCode, "please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services"))
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services"))
|
||||
}
|
||||
|
||||
118
pkg/service/v0/users.go
Normal file
118
pkg/service/v0/users.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
|
||||
"github.com/cs3org/reva/pkg/user"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
|
||||
"github.com/micro/go-micro/v2/client/grpc"
|
||||
merrors "github.com/micro/go-micro/v2/errors"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/response"
|
||||
storepb "github.com/owncloud/ocis-store/pkg/proto/v0"
|
||||
)
|
||||
|
||||
// GetUser returns the currently logged in user
|
||||
func (o Ocs) GetUser(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
userid := chi.URLParam(r, "userid")
|
||||
|
||||
if userid == "" {
|
||||
u, ok := user.ContextGetUser(r.Context()) // TODO HERE
|
||||
if !ok {
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context"))
|
||||
return
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
||||
accSvcID := "com.owncloud.api.accounts" // TODO query the registry and filter using labels
|
||||
accSvc := accounts.NewAccountsService(accSvcID, grpc.NewClient())
|
||||
resp, err := accSvc.ListAccounts(c.Context, &accounts.ListAccountsRequest{})
|
||||
*/
|
||||
render.Render(w, r, response.DataRender(&data.User{
|
||||
ID: u.Username, // TODO userid vs username! implications for clients if we return the userid here? -> implement graph ASAP?
|
||||
DisplayName: u.DisplayName,
|
||||
Email: u.Mail,
|
||||
}))
|
||||
}
|
||||
|
||||
// GetSigningKey returns the signing key for the current user. It will create it on the fly if it does not exist
|
||||
// The signing key is part of the user settings and is used by the proxy to authenticate requests
|
||||
// Currently, the username is used as the OC-Credential
|
||||
func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) {
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
//o.logger.Error().Msg("missing user in context")
|
||||
render.Render(w, r, response.ErrRender(data.MetaBadRequest.StatusCode, "missing user in context"))
|
||||
return
|
||||
}
|
||||
c := storepb.NewStoreService("com.owncloud.api.store", grpc.NewClient())
|
||||
res, err := c.Read(r.Context(), &storepb.ReadRequest{
|
||||
Options: &storepb.ReadOptions{
|
||||
Database: "proxy",
|
||||
Table: "signing-keys",
|
||||
},
|
||||
Key: u.Username,
|
||||
})
|
||||
if err == nil && len(res.Records) > 0 {
|
||||
render.Render(w, r, response.DataRender(&data.SigningKey{
|
||||
User: u.Username,
|
||||
SigningKey: string(res.Records[0].Value),
|
||||
}))
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
e := merrors.Parse(err.Error())
|
||||
if e.Code == http.StatusNotFound {
|
||||
//o.logger.Debug().Str("username", u.Username).Msg("signing key not found")
|
||||
// not found is ok, so we can continue and generate the key on the fly
|
||||
} else {
|
||||
//o.logger.Err(err).Msg("error reading from store")
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "error reading from store"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// try creating it
|
||||
key := make([]byte, 64)
|
||||
_, err = rand.Read(key[:])
|
||||
if err != nil {
|
||||
//o.logger.Error().Err(err).Msg("could not generate signing key")
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not generate signing key"))
|
||||
return
|
||||
}
|
||||
signingKey := hex.EncodeToString(key)
|
||||
|
||||
_, err = c.Write(r.Context(), &storepb.WriteRequest{
|
||||
Options: &storepb.WriteOptions{
|
||||
Database: "proxy",
|
||||
Table: "signing-keys",
|
||||
},
|
||||
Record: &storepb.Record{
|
||||
Key: u.Username,
|
||||
Value: []byte(signingKey),
|
||||
// TODO Expiry?
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
//o.logger.Error().Err(err).Msg("error writing key")
|
||||
render.Render(w, r, response.ErrRender(data.MetaServerError.StatusCode, "could not persist signing key"))
|
||||
return
|
||||
}
|
||||
|
||||
render.Render(w, r, response.DataRender(&data.SigningKey{
|
||||
User: u.Username,
|
||||
SigningKey: signingKey,
|
||||
}))
|
||||
}
|
||||
|
||||
// ListUsers lists the users
|
||||
func (o Ocs) ListUsers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
render.Render(w, r, response.ErrRender(data.MetaUnknownError.StatusCode, "please check the syntax. API specifications are here: http://www.freedesktop.org/wiki/Specifications/open-collaboration-services"))
|
||||
}
|
||||
Reference in New Issue
Block a user