Files
kopia/internal/server/api_content.go
Jarek Kowalski 689ed0a851 server: refactored authentication and authorization (#871)
This formalizes the concept of a 'UI user' which is a local
user that can call APIs the same way that UI does it.

The server will now allow access to:

- UI user (identified using `--server-username` with password specified
  using `--server-password' or `--random-password`)
- remote users with usersnames/passwords specified in `--htpasswd-file`
- remote users defined in the repository using `kopia users add`
  when `--allow-repository-users` is passed.

The UI user only has access to methods specifically designated as such
(normally APIs used by the UI + few special ones such as 'shutdown').

Remote users (identified via `user@host`) don't get access to UI APIs.

There are some APIs that can be accessed by any authenticated
caller (UI or remote):

- /api/v1/flush
- /api/v1/repo/status
- /api/v1/repo/sync
- /api/v1/repo/parameters

To make this easier to understand in code, refactored server handlers
to require specifing what kind of authorization is required
at registration time.
2021-03-08 22:25:22 -08:00

80 lines
1.9 KiB
Go

package server
import (
"context"
"errors"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/repo/content"
"github.com/kopia/kopia/repo/manifest"
)
func (s *Server) handleContentGet(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
dr, ok := s.rep.(repo.DirectRepository)
if !ok {
return nil, notFoundError("content not found")
}
cid := content.ID(mux.Vars(r)["contentID"])
data, err := dr.ContentReader().GetContent(ctx, cid)
if errors.Is(err, content.ErrContentNotFound) {
return nil, notFoundError("content not found")
}
return data, nil
}
func (s *Server) handleContentInfo(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
dr, ok := s.rep.(repo.DirectRepository)
if !ok {
return nil, notFoundError("content not found")
}
cid := content.ID(mux.Vars(r)["contentID"])
ci, err := dr.ContentReader().ContentInfo(ctx, cid)
switch {
case err == nil:
return ci, nil
case errors.Is(err, content.ErrContentNotFound):
return nil, notFoundError("content not found")
default:
return nil, internalServerError(err)
}
}
func (s *Server) handleContentPut(ctx context.Context, r *http.Request, data []byte) (interface{}, *apiError) {
dr, ok := s.rep.(repo.DirectRepositoryWriter)
if !ok {
return nil, repositoryNotWritableError()
}
cid := content.ID(mux.Vars(r)["contentID"])
prefix := cid.Prefix()
if strings.HasPrefix(string(prefix), manifest.ContentPrefix) {
// it's not allowed to create contents prefixed with 'm' since those could be mistaken for manifest contents.
return nil, accessDeniedError()
}
actualCID, err := dr.ContentManager().WriteContent(ctx, data, prefix)
if err != nil {
return nil, internalServerError(err)
}
if actualCID != cid {
return nil, requestError(serverapi.ErrorMalformedRequest, "mismatched content ID")
}
return &serverapi.Empty{}, nil
}