mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-25 21:48:28 -05:00
Initial OCS routes, version & format handling
Signed-off-by: Jörn Friedrich Dreyer <jfd@butonic.de>
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
// OCSFormatCtx middleware is used to determine the content type from
|
||||
// the format URL parameter passed in an ocs request. Defaults to XML
|
||||
func OCSFormatCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Query().Get("format") {
|
||||
case "", "xml":
|
||||
r.Header.Set("Accept", "application/xml")
|
||||
r = r.WithContext(context.WithValue(r.Context(), render.ContentTypeCtxKey, render.ContentTypeXML))
|
||||
case "json":
|
||||
r.Header.Set("Accept", "application/json")
|
||||
r = r.WithContext(context.WithValue(r.Context(), render.ContentTypeCtxKey, render.ContentTypeJSON))
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
"github.com/owncloud/ocis-ocs/pkg/service/v0/data"
|
||||
)
|
||||
|
||||
// GetConfig renders the ocs config endpoint
|
||||
func (o Ocs) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
render.Render(w, r, DataRender(&data.ConfigData{
|
||||
Version: "1.7", // TODO get from env
|
||||
Website: "ocis", // TODO get from env
|
||||
Host: "", // TODO get from FRONTEND config
|
||||
Contact: "", // TODO get from env
|
||||
SSL: "true", // TODO get from env
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
)
|
||||
|
||||
// ocsBool implements the xml/json Marshaler interface. The OCS API inconsistency require us to parse boolean values
|
||||
// as native booleans for json requests but "truthy" 0/1 values for xml requests.
|
||||
type ocsBool bool
|
||||
|
||||
func (c *ocsBool) MarshalJSON() ([]byte, error) {
|
||||
if *c {
|
||||
return []byte("true"), nil
|
||||
}
|
||||
|
||||
return []byte("false"), nil
|
||||
}
|
||||
|
||||
func (c ocsBool) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||
if c {
|
||||
return e.EncodeElement("1", start)
|
||||
}
|
||||
|
||||
return e.EncodeElement("0", start)
|
||||
}
|
||||
|
||||
// CapabilitiesData TODO document
|
||||
type CapabilitiesData struct {
|
||||
Capabilities *Capabilities `json:"capabilities" xml:"capabilities"`
|
||||
Version *Version `json:"version" xml:"version"`
|
||||
}
|
||||
|
||||
// Capabilities groups several capability aspects
|
||||
type Capabilities struct {
|
||||
Core *CapabilitiesCore `json:"core" xml:"core"`
|
||||
Checksums *CapabilitiesChecksums `json:"checksums" xml:"checksums"`
|
||||
Files *CapabilitiesFiles `json:"files" xml:"files" mapstructure:"files"`
|
||||
Dav *CapabilitiesDav `json:"dav" xml:"dav"`
|
||||
FilesSharing *CapabilitiesFilesSharing `json:"files_sharing" xml:"files_sharing" mapstructure:"files_sharing"`
|
||||
Notifications *CapabilitiesNotifications `json:"notifications" xml:"notifications"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Status holds basic status information
|
||||
type Status struct {
|
||||
Installed ocsBool `json:"installed" xml:"installed"`
|
||||
Maintenance ocsBool `json:"maintenance" xml:"maintenance"`
|
||||
NeedsDBUpgrade ocsBool `json:"needsDbUpgrade" xml:"needsDbUpgrade"`
|
||||
Version string `json:"version" xml:"version"`
|
||||
VersionString string `json:"versionstring" xml:"versionstring"`
|
||||
Edition string `json:"edition" xml:"edition"`
|
||||
ProductName string `json:"productname" xml:"productname"`
|
||||
Hostname string `json:"hostname,omitempty" xml:"hostname,omitempty"`
|
||||
}
|
||||
|
||||
// CapabilitiesChecksums holds available hashes
|
||||
type CapabilitiesChecksums struct {
|
||||
SupportedTypes []string `json:"supportedTypes" xml:"supportedTypes>element" mapstructure:"supported_types"`
|
||||
PreferredUploadType string `json:"preferredUploadType" xml:"preferredUploadType" mapstructure:"preferred_upload_type"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesTusSupport TODO this must be a summary of storages
|
||||
type CapabilitiesFilesTusSupport struct {
|
||||
Version string `json:"version" xml:"version"`
|
||||
Resumable string `json:"resumable" xml:"resumable"`
|
||||
Extension string `json:"extension" xml:"extension"`
|
||||
MaxChunkSize int `json:"max_chunk_size" xml:"max_chunk_size" mapstructure:"max_chunk_size"`
|
||||
HTTPMethodOverride string `json:"http_method_override" xml:"http_method_override" mapstructure:"http_method_override"`
|
||||
}
|
||||
|
||||
// CapabilitiesFiles TODO this is storage specific, not global. What effect do these options have on the clients?
|
||||
type CapabilitiesFiles struct {
|
||||
PrivateLinks ocsBool `json:"privateLinks" xml:"privateLinks" mapstructure:"private_links"`
|
||||
BigFileChunking ocsBool `json:"bigfilechunking" xml:"bigfilechunking"`
|
||||
Undelete ocsBool `json:"undelete" xml:"undelete"`
|
||||
Versioning ocsBool `json:"versioning" xml:"versioning"`
|
||||
BlacklistedFiles []string `json:"blacklisted_files" xml:"blacklisted_files>element" mapstructure:"blacklisted_files"`
|
||||
TusSupport *CapabilitiesFilesTusSupport `json:"tus_support" xml:"tus_support" mapstructure:"tus_support"`
|
||||
}
|
||||
|
||||
// CapabilitiesDav holds dav endpoint config
|
||||
type CapabilitiesDav struct {
|
||||
Chunking string `json:"chunking" xml:"chunking"`
|
||||
Trashbin string `json:"trashbin" xml:"trashbin"`
|
||||
Reports []string `json:"reports" xml:"reports>element" mapstructure:"reports"`
|
||||
ChunkingParallelUploadDisabled bool `json:"chunkingParallelUploadDisabled" xml:"chunkingParallelUploadDisabled"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharing TODO document
|
||||
type CapabilitiesFilesSharing struct {
|
||||
APIEnabled ocsBool `json:"api_enabled" xml:"api_enabled" mapstructure:"api_enabled"`
|
||||
Resharing ocsBool `json:"resharing" xml:"resharing"`
|
||||
GroupSharing ocsBool `json:"group_sharing" xml:"group_sharing" mapstructure:"group_sharing"`
|
||||
AutoAcceptShare ocsBool `json:"auto_accept_share" xml:"auto_accept_share" mapstructure:"auto_accept_share"`
|
||||
ShareWithGroupMembersOnly ocsBool `json:"share_with_group_members_only" xml:"share_with_group_members_only" mapstructure:"share_with_group_members_only"`
|
||||
ShareWithMembershipGroupsOnly ocsBool `json:"share_with_membership_groups_only" xml:"share_with_membership_groups_only" mapstructure:"share_with_membership_groups_only"`
|
||||
SearchMinLength int `json:"search_min_length" xml:"search_min_length" mapstructure:"search_min_length"`
|
||||
DefaultPermissions int `json:"default_permissions" xml:"default_permissions" mapstructure:"default_permissions"`
|
||||
UserEnumeration *CapabilitiesFilesSharingUserEnumeration `json:"user_enumeration" xml:"user_enumeration" mapstructure:"user_enumeration"`
|
||||
Federation *CapabilitiesFilesSharingFederation `json:"federation" xml:"federation"`
|
||||
Public *CapabilitiesFilesSharingPublic `json:"public" xml:"public"`
|
||||
User *CapabilitiesFilesSharingUser `json:"user" xml:"user"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingPublic TODO document
|
||||
type CapabilitiesFilesSharingPublic struct {
|
||||
Enabled ocsBool `json:"enabled" xml:"enabled"`
|
||||
SendMail ocsBool `json:"send_mail" xml:"send_mail" mapstructure:"send_mail"`
|
||||
SocialShare ocsBool `json:"social_share" xml:"social_share" mapstructure:"social_share"`
|
||||
Upload ocsBool `json:"upload" xml:"upload"`
|
||||
Multiple ocsBool `json:"multiple" xml:"multiple"`
|
||||
SupportsUploadOnly ocsBool `json:"supports_upload_only" xml:"supports_upload_only" mapstructure:"supports_upload_only"`
|
||||
Password *CapabilitiesFilesSharingPublicPassword `json:"password" xml:"password"`
|
||||
ExpireDate *CapabilitiesFilesSharingPublicExpireDate `json:"expire_date" xml:"expire_date" mapstructure:"expire_date"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingPublicPassword TODO document
|
||||
type CapabilitiesFilesSharingPublicPassword struct {
|
||||
EnforcedFor *CapabilitiesFilesSharingPublicPasswordEnforcedFor `json:"enforced_for" xml:"enforced_for" mapstructure:"enforced_for"`
|
||||
Enforced ocsBool `json:"enforced" xml:"enforced"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingPublicPasswordEnforcedFor TODO document
|
||||
type CapabilitiesFilesSharingPublicPasswordEnforcedFor struct {
|
||||
ReadOnly ocsBool `json:"read_only" xml:"read_only,omitempty" mapstructure:"read_only"`
|
||||
ReadWrite ocsBool `json:"read_write" xml:"read_write,omitempty" mapstructure:"read_write"`
|
||||
UploadOnly ocsBool `json:"upload_only" xml:"upload_only,omitempty" mapstructure:"upload_only"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingPublicExpireDate TODO document
|
||||
type CapabilitiesFilesSharingPublicExpireDate struct {
|
||||
Enabled ocsBool `json:"enabled" xml:"enabled"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingUser TODO document
|
||||
type CapabilitiesFilesSharingUser struct {
|
||||
SendMail ocsBool `json:"send_mail" xml:"send_mail" mapstructure:"send_mail"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingUserEnumeration TODO document
|
||||
type CapabilitiesFilesSharingUserEnumeration struct {
|
||||
Enabled ocsBool `json:"enabled" xml:"enabled"`
|
||||
GroupMembersOnly ocsBool `json:"group_members_only" xml:"group_members_only" mapstructure:"group_members_only"`
|
||||
}
|
||||
|
||||
// CapabilitiesFilesSharingFederation holds outgoing and incoming flags
|
||||
type CapabilitiesFilesSharingFederation struct {
|
||||
Outgoing ocsBool `json:"outgoing" xml:"outgoing"`
|
||||
Incoming ocsBool `json:"incoming" xml:"incoming"`
|
||||
}
|
||||
|
||||
// CapabilitiesNotifications holds a list of notification endpoints
|
||||
type CapabilitiesNotifications struct {
|
||||
Endpoints []string `json:"ocs-endpoints" xml:"ocs-endpoints>element" mapstructure:"endpoints"`
|
||||
}
|
||||
|
||||
// Version holds version information
|
||||
type Version struct {
|
||||
Major int `json:"major" xml:"major"`
|
||||
Minor int `json:"minor" xml:"minor"`
|
||||
Micro int `json:"micro" xml:"micro"` // = patch level
|
||||
String string `json:"string" xml:"string"`
|
||||
Edition string `json:"edition" xml:"edition"`
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package data
|
||||
|
||||
// ConfigData holds basic config
|
||||
type ConfigData struct {
|
||||
Version string `json:"version" xml:"version"`
|
||||
Website string `json:"website" xml:"website"`
|
||||
Host string `json:"host" xml:"host"`
|
||||
Contact string `json:"contact" xml:"contact"`
|
||||
SSL string `json:"ssl" xml:"ssl"`
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package data
|
||||
|
||||
// User holds the payload for a GetUser response
|
||||
type User struct {
|
||||
// TODO needs better naming, clarify if we need a userid, a username or both
|
||||
ID string `json:"id" xml:"id"`
|
||||
DisplayName string `json:"display-name" xml:"display-name"`
|
||||
Email string `json:"email" xml:"email"`
|
||||
}
|
||||
|
||||
// SigningKey holds the Payload for a GetSigningKey response
|
||||
type SigningKey struct {
|
||||
User string `json:"user" xml:"user"`
|
||||
SigningKey string `json:"signing-key" xml:"signing-key"`
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func (i instrument) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Dummy implements the Service interface.
|
||||
func (i instrument) Dummy(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.Dummy(w, r)
|
||||
// GetConfig implements the Service interface.
|
||||
func (i instrument) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
i.next.GetConfig(w, r)
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func (l logging) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Dummy implements the Service interface.
|
||||
func (l logging) Dummy(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.Dummy(w, r)
|
||||
// GetConfig implements the Service interface.
|
||||
func (l logging) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
l.next.GetConfig(w, r)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
// Response is the top level response structure
|
||||
type Response struct {
|
||||
OCS *Payload `json:"ocs"`
|
||||
}
|
||||
|
||||
var (
|
||||
elementStartElement = xml.StartElement{Name: xml.Name{Local: "element"}}
|
||||
metaStartElement = xml.StartElement{Name: xml.Name{Local: "meta"}}
|
||||
ocsName = xml.Name{Local: "ocs"}
|
||||
dataName = xml.Name{Local: "data"}
|
||||
)
|
||||
|
||||
// Payload combines response metadata and data
|
||||
type Payload struct {
|
||||
XMLName struct{} `json:"-" xml:"ocs"`
|
||||
Meta Meta `json:"meta" xml:"meta"`
|
||||
Data interface{} `json:"data,omitempty" xml:"data,omitempty"`
|
||||
}
|
||||
|
||||
// MarshalXML handles ocs specific wrapping of array members in 'element' tags for the data
|
||||
func (p Payload) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
|
||||
// first the easy part
|
||||
// use ocs as the surrounding tag
|
||||
start.Name = ocsName
|
||||
if err = e.EncodeToken(start); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// encode the meta tag
|
||||
if err = e.EncodeElement(p.Meta, metaStartElement); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// we need to use reflection to determine if p.Data is an array or a slice
|
||||
rt := reflect.TypeOf(p.Data)
|
||||
if rt != nil && (rt.Kind() == reflect.Array || rt.Kind() == reflect.Slice) {
|
||||
// this is how to wrap the data elements in their own <element> tag
|
||||
v := reflect.ValueOf(p.Data)
|
||||
if err = e.EncodeToken(xml.StartElement{Name: dataName}); err != nil {
|
||||
return
|
||||
}
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if err = e.EncodeElement(v.Index(i).Interface(), elementStartElement); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err = e.EncodeToken(xml.EndElement{Name: dataName}); err != nil {
|
||||
return
|
||||
}
|
||||
} else if err = e.EncodeElement(p.Data, xml.StartElement{Name: dataName}); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// write the closing <ocs> tag
|
||||
if err = e.EncodeToken(xml.EndElement{Name: start.Name}); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Render sets the status code of the http response, taking the ocs version into account
|
||||
func (p *Payload) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
version := APIVersion(r.Context())
|
||||
m := statusCodeMapper(version)
|
||||
statusCode := m(p.Meta)
|
||||
render.Status(r, statusCode)
|
||||
if version == ocsVersion2 && statusCode == http.StatusOK {
|
||||
p.Meta.StatusCode = statusCode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DataRender creates an OK Payload for the given data
|
||||
func DataRender(d interface{}) render.Renderer {
|
||||
return &Payload{
|
||||
Meta: MetaOK,
|
||||
Data: d,
|
||||
}
|
||||
}
|
||||
|
||||
// ErrRender creates an Error Paylod with the given OCS error code and message
|
||||
// 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},
|
||||
}
|
||||
}
|
||||
|
||||
func statusCodeMapper(version string) func(Meta) int {
|
||||
var mapper func(Meta) int
|
||||
switch version {
|
||||
case ocsVersion1:
|
||||
mapper = OcsV1StatusCodes
|
||||
case ocsVersion2:
|
||||
mapper = OcsV2StatusCodes
|
||||
default:
|
||||
mapper = defaultStatusCodeMapper
|
||||
}
|
||||
return mapper
|
||||
}
|
||||
+83
-10
@@ -3,14 +3,20 @@ package svc
|
||||
import (
|
||||
"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/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-pkg/v2/log"
|
||||
)
|
||||
|
||||
// Service defines the extension handlers.
|
||||
type Service interface {
|
||||
ServeHTTP(http.ResponseWriter, *http.Request)
|
||||
Dummy(http.ResponseWriter, *http.Request)
|
||||
GetConfig(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
// NewService returns a service implementation for Service.
|
||||
@@ -23,10 +29,31 @@ func NewService(opts ...Option) Service {
|
||||
svc := Ocs{
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
logger: options.Logger,
|
||||
}
|
||||
|
||||
m.Route(options.Config.HTTP.Root, func(r chi.Router) {
|
||||
r.Get("/", svc.Dummy)
|
||||
r.NotFound(svc.NotFound)
|
||||
r.Use(middleware.StripSlashes)
|
||||
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.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) {
|
||||
r.Route("/capabilities", func(r chi.Router) {})
|
||||
r.Route("/user", func(r chi.Router) {
|
||||
r.Get("/", svc.GetUser)
|
||||
r.Get("/signing-key", svc.GetSigningKey)
|
||||
})
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", svc.ListUsers)
|
||||
})
|
||||
})
|
||||
r.Route("/config", func(r chi.Router) {
|
||||
r.Get("/", svc.GetConfig)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return svc
|
||||
@@ -35,18 +62,64 @@ func NewService(opts ...Option) Service {
|
||||
// Ocs defines implements the business logic for Service.
|
||||
type Ocs struct {
|
||||
config *config.Config
|
||||
logger log.Logger
|
||||
mux *chi.Mux
|
||||
}
|
||||
|
||||
// ServeHTTP implements the Service interface.
|
||||
func (g Ocs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
g.mux.ServeHTTP(w, r)
|
||||
func (o Ocs) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
o.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Dummy implements the Service interface.
|
||||
func (g Ocs) Dummy(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
w.Write([]byte(http.StatusText(http.StatusOK)))
|
||||
// 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) {
|
||||
|
||||
// TODO move token marshaling to ocis-proxy
|
||||
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
|
||||
// TODO middleware for the proxy
|
||||
func (o Ocs) GetSigningKey(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// TODO move token marshaling to ocis-proxy
|
||||
u, ok := user.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
render.Render(w, r, ErrRender(MetaBadRequest.StatusCode, "missing user in context"))
|
||||
return
|
||||
}
|
||||
|
||||
signingKey := "TODO fetch from settings" // TODO fetch from settings
|
||||
/*
|
||||
if ($signingKey === null) {
|
||||
$signingKey = \OC::$server->getSecureRandom()->generate(64);
|
||||
\OC::$server->getConfig()->setUserValue($userId, 'core', 'signing-key', $signingKey, null);
|
||||
}
|
||||
*/
|
||||
|
||||
render.Render(w, r, DataRender(&data.SigningKey{
|
||||
User: u.Username, // TODO userid vs 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"))
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func (t tracing) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
middleware.Trace(t.next).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// Dummy implements the Service interface.
|
||||
func (t tracing) Dummy(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.Dummy(w, r)
|
||||
// GetConfig implements the Service interface.
|
||||
func (t tracing) GetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
t.next.GetConfig(w, r)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
type key int
|
||||
|
||||
const (
|
||||
apiVersionKey key = iota
|
||||
ocsVersion1 = "1"
|
||||
ocsVersion2 = "2"
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return http.StatusOK
|
||||
}
|
||||
|
||||
// OcsV2StatusCodes maps the OCS codes to http status codes for the ocs API v2.
|
||||
func OcsV2StatusCodes(meta Meta) int {
|
||||
sc := meta.StatusCode
|
||||
switch sc {
|
||||
case MetaNotFound.StatusCode:
|
||||
return http.StatusNotFound
|
||||
case MetaUnknownError.StatusCode:
|
||||
fallthrough
|
||||
case MetaServerError.StatusCode:
|
||||
return http.StatusInternalServerError
|
||||
case MetaUnauthorized.StatusCode:
|
||||
return http.StatusUnauthorized
|
||||
case 100:
|
||||
meta.StatusCode = http.StatusOK
|
||||
return http.StatusOK
|
||||
}
|
||||
// any 2xx, 4xx and 5xx will be used as is
|
||||
if sc >= 200 && sc < 600 {
|
||||
return sc
|
||||
}
|
||||
|
||||
// any error codes > 100 are treated as client errors
|
||||
if sc > 100 && sc < 200 {
|
||||
return http.StatusBadRequest
|
||||
}
|
||||
|
||||
// TODO change this status code?
|
||||
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 {
|
||||
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"))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Ocs-Api-Version", version)
|
||||
|
||||
// store version in context so handlers can access it
|
||||
ctx := context.WithValue(r.Context(), apiVersionKey, version)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user