Document consumer README skeleton and add package Examples.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# go-nc-exapp
|
# go-nc-exapp
|
||||||
|
|
||||||
Shared Go library for Nextcloud ExApp Services: AppAPI authentication, OCS JSON calls, per-user ExApp preferences, and optional Required Groups Access Gate.
|
Shared Go library for Nextcloud ExApp Services: AppAPI authentication, OCS JSON calls, per-user ExApp preferences, and an optional Required Groups Access Gate.
|
||||||
|
|
||||||
Import: `gitea.neitzel.de/konrad/go-nc-exapp`
|
Import: `gitea.neitzel.de/konrad/go-nc-exapp` (package `gonexapp`).
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ Import: `gitea.neitzel.de/konrad/go-nc-exapp`
|
|||||||
- **UserFromRequest** — extract the requesting user from inbound AppAPI-proxied requests
|
- **UserFromRequest** — extract the requesting user from inbound AppAPI-proxied requests
|
||||||
- **OCSClient** — authenticated OCS calls that always append `format=json`
|
- **OCSClient** — authenticated OCS calls that always append `format=json`
|
||||||
- **AppAPIPreferences** — parameterized get/set of a string ExApp preference (caller supplies app id and key)
|
- **AppAPIPreferences** — parameterized get/set of a string ExApp preference (caller supplies app id and key)
|
||||||
- **Access Gate** — optional Required Groups enforcement (`Wrap` + `Check`), English denied HTML, positive membership cache; env helpers for `REQUIRED_GROUPS` / `REQUIRED_GROUPS_CACHE_SECONDS`
|
- **Access Gate** — optional Required Groups enforcement (`Wrap` + `Check`), English denied HTML for browsers, positive membership cache; env helpers for `REQUIRED_GROUPS` / `REQUIRED_GROUPS_CACHE_SECONDS`
|
||||||
|
|
||||||
**Excluded**
|
**Excluded**
|
||||||
|
|
||||||
@@ -49,10 +49,16 @@ Each ExApp chooses its own preference keys; this library does not hardcode produ
|
|||||||
|
|
||||||
Declare `REQUIRED_GROUPS` and `REQUIRED_GROUPS_CACHE_SECONDS` in the ExApp `info.xml` so Deploy options can set them.
|
Declare `REQUIRED_GROUPS` and `REQUIRED_GROUPS_CACHE_SECONDS` in the ExApp `info.xml` so Deploy options can set them.
|
||||||
|
|
||||||
|
Runnable package examples: `go test -run Example`.
|
||||||
|
|
||||||
## Domain language
|
## Domain language
|
||||||
|
|
||||||
See [CONTEXT.md](./CONTEXT.md) for AppAPI credentials, Requesting user, ExApp preference, OCS, Required Groups, and Access Gate terminology.
|
See [CONTEXT.md](./CONTEXT.md) for AppAPI credentials, Requesting user, ExApp preference, OCS, Required Groups, and Access Gate terminology.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Unit tests use `httptest` fake OCS servers. No live Nextcloud is required for Library CI.
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- **go-nc-files** — WebDAV, Working Folder, Saved Default, Visit resolution
|
- **go-nc-files** — WebDAV, Working Folder, Saved Default, Visit resolution
|
||||||
|
|||||||
@@ -32,9 +32,13 @@ type cacheEntry struct {
|
|||||||
type CheckResult int
|
type CheckResult int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// CheckAllowed means the request may proceed (or the gate is inactive / skipped).
|
||||||
CheckAllowed CheckResult = iota
|
CheckAllowed CheckResult = iota
|
||||||
|
// CheckDenied means the Requesting user is not in Required Groups.
|
||||||
CheckDenied
|
CheckDenied
|
||||||
|
// CheckUnauthorized means no Requesting user could be read from the request.
|
||||||
CheckUnauthorized
|
CheckUnauthorized
|
||||||
|
// CheckUnavailable means group membership could not be determined (e.g. OCS error).
|
||||||
CheckUnavailable
|
CheckUnavailable
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package gonexapp_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
gonexapp "gitea.neitzel.de/konrad/go-nc-exapp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleUserFromRequest() {
|
||||||
|
token := base64.StdEncoding.EncodeToString([]byte("alice:secret"))
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api", nil)
|
||||||
|
req.Header.Set("AUTHORIZATION-APP-API", token)
|
||||||
|
|
||||||
|
user, err := gonexapp.UserFromRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("err:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(user)
|
||||||
|
// Output: alice
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleCredentials_AuthHeaders() {
|
||||||
|
cred := gonexapp.Credentials{
|
||||||
|
BaseURL: "https://nextcloud.example", AppID: "myexapp", AppVersion: "0.1.0",
|
||||||
|
AAVersion: "1.0.0", AppSecret: "s", UserID: "alice",
|
||||||
|
}
|
||||||
|
h := cred.AuthHeaders()
|
||||||
|
fmt.Println(h.Get("EX-APP-ID"), h.Get("OCS-APIRequest") != "")
|
||||||
|
// Output: myexapp true
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleNewAppAPIPreferences() {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if strings.Contains(r.URL.Path, "get-values") {
|
||||||
|
_, _ = io.WriteString(w, `{"ocs":{"data":[{"configkey":"savedDefault","configvalue":"Zones"}]}}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = io.WriteString(w, `{"ocs":{"data":{}}}`)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
cred := gonexapp.Credentials{
|
||||||
|
BaseURL: srv.URL, AppID: "myexapp", AppVersion: "0.1.0", AAVersion: "1.0.0",
|
||||||
|
AppSecret: "s", UserID: "alice",
|
||||||
|
}
|
||||||
|
prefs := gonexapp.NewAppAPIPreferences(cred, "myexapp", "savedDefault")
|
||||||
|
prefs.Client = srv.Client()
|
||||||
|
prefs.OCS.Client = srv.Client()
|
||||||
|
|
||||||
|
value, err := prefs.Get()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("err:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := prefs.Set("Zones"); err != nil {
|
||||||
|
fmt.Println("set:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(value)
|
||||||
|
// Output: Zones
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAccessGate_Wrap() {
|
||||||
|
inner := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
_, _ = io.WriteString(w, "ok")
|
||||||
|
})
|
||||||
|
// Empty Required Groups: gate is inactive.
|
||||||
|
h := gonexapp.AccessGate{}.Wrap(inner)
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api", nil))
|
||||||
|
fmt.Println(rec.Code, rec.Body.String())
|
||||||
|
// Output: 200 ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleResolveRequiredGroups() {
|
||||||
|
fmt.Println(gonexapp.ResolveRequiredGroups("", false, nil))
|
||||||
|
fmt.Println(len(gonexapp.ResolveRequiredGroups("", true, []string{"ops"})))
|
||||||
|
// Output:
|
||||||
|
// []
|
||||||
|
// 0
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user