ultisuite-backend/internal/api/office/public_share.go
R3D347HR4Y 556d5f416d Enhance API and configuration for contact discovery and public sharing
- Introduced new endpoints for contact discovery, including scanning, listing, and managing discovered contacts.
- Implemented retry logic for handling missing DAV credentials during contact operations.
- Added public share functionality for drive API, allowing users to manage public shares, including upload, delete, and rename operations.
- Updated Nextcloud configuration to support public share links and improved error handling for public share permissions.
- Enhanced logging and validation across contact and drive APIs for better error tracking and user feedback.
- Added tests for new contact matching and ranking functionalities to ensure accuracy and reliability.
2026-06-06 20:27:02 +02:00

112 lines
3.1 KiB
Go

package office
import (
"context"
"fmt"
"io"
"net/url"
"strings"
"time"
)
type PublicShareAccess struct {
Token string
FilePath string
Password string
}
func (s *Service) PublicEditorConfig(ctx context.Context, token, filePath, mode, password, guestID string) (map[string]any, error) {
token = strings.TrimSpace(token)
filePath = normalizePath(filePath)
if token == "" || filePath == "" {
return nil, fmt.Errorf("invalid public office session")
}
if mode == "" {
mode = "edit"
}
rev, err := s.nc.PublicShareFileRevision(ctx, token, filePath, password)
if err != nil {
return nil, fmt.Errorf("resolve public file revision: %w", err)
}
apiBase := strings.TrimRight(s.Cfg.APIInternalURL, "/")
sig, err := signPublicDocAccess(token, filePath, password, s.Cfg.JWTSecret)
if err != nil {
return nil, err
}
downloadURL := buildPublicOfficeEndpointURL(apiBase, token, "/office/document", filePath, password, sig)
callbackURL := buildPublicOfficeEndpointURL(apiBase, token, "/office/callback", filePath, password, sig)
editorUserID := strings.TrimSpace(guestID)
if editorUserID == "" {
editorUserID = "public-guest"
} else {
editorUserID = "public:" + editorUserID
}
config, err := buildEditorConfig(buildEditorConfigInput{
filePath: filePath,
mode: mode,
editorUserID: editorUserID,
userName: "Invité",
documentKey: s.keys.current(rev.FileID),
downloadURL: downloadURL,
callbackURL: callbackURL,
})
if err != nil {
return nil, err
}
return wrapConfig(config, s.Cfg.JWTSecret)
}
func (s *Service) OpenPublicDocument(ctx context.Context, access PublicShareAccess) (io.ReadCloser, string, error) {
return s.nc.DownloadPublicShare(ctx, access.Token, access.FilePath, access.Password)
}
func (s *Service) SavePublicDocument(ctx context.Context, access PublicShareAccess, body io.Reader, contentType string) error {
return s.nc.UploadPublicShare(ctx, access.Token, access.FilePath, access.Password, body, contentType)
}
func buildPublicOfficeEndpointURL(base, token, endpoint, filePath, password, sig string) string {
q := url.Values{}
q.Set("path", normalizePath(filePath))
if password != "" {
q.Set("password", password)
}
if sig != "" {
q.Set("sig", sig)
}
return strings.TrimRight(base, "/") + "/api/v1/drive/public/shares/" + url.PathEscape(token) + endpoint + "?" + q.Encode()
}
func signPublicDocAccess(token, filePath, password, secret string) (string, error) {
payload := map[string]any{
"token": strings.TrimSpace(token),
"path": normalizePath(filePath),
"password": password,
"exp": time.Now().Add(2 * time.Hour).Unix(),
}
return signJWT(payload, secret)
}
func VerifyPublicDocAccess(token, filePath, password, sig, secret string) bool {
if secret == "" {
return true
}
payload, err := verifyJWT(sig, secret)
if err != nil {
return false
}
if payload["token"] != strings.TrimSpace(token) || payload["path"] != normalizePath(filePath) {
return false
}
if pw, _ := payload["password"].(string); pw != password {
return false
}
if exp, ok := payload["exp"].(float64); ok && int64(exp) < time.Now().Unix() {
return false
}
return true
}