Introduce Reservation

This commit is contained in:
binwiederhier 2023-01-02 20:08:37 -05:00
parent 1256ba0429
commit 1733323132
11 changed files with 194 additions and 109 deletions

View file

@ -1308,7 +1308,7 @@ func (s *Server) runFirebaseKeepaliver() {
if s.firebaseClient == nil {
return
}
v := newVisitor(s.config, s.messageCache, netip.IPv4Unspecified(), nil) // Background process, not a real visitor, uses IP 0.0.0.0
v := newVisitor(s.config, s.messageCache, s.userManager, netip.IPv4Unspecified(), nil) // Background process, not a real visitor, uses IP 0.0.0.0
for {
select {
case <-time.After(s.config.FirebaseKeepaliveInterval):
@ -1579,7 +1579,7 @@ func (s *Server) visitorFromID(visitorID string, ip netip.Addr, user *user.User)
defer s.mu.Unlock()
v, exists := s.visitors[visitorID]
if !exists {
s.visitors[visitorID] = newVisitor(s.config, s.messageCache, ip, user)
s.visitors[visitorID] = newVisitor(s.config, s.messageCache, s.userManager, ip, user)
return s.visitors[visitorID]
}
v.Keepalive()

View file

@ -94,16 +94,27 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *vis
Upgradable: true,
}
}
if len(v.user.Grants) > 0 {
response.Access = make([]*apiAccountGrant, 0)
for _, grant := range v.user.Grants {
if grant.Owner {
response.Access = append(response.Access, &apiAccountGrant{
Topic: grant.TopicPattern,
Read: grant.AllowRead,
Write: grant.AllowWrite,
})
reservations, err := s.userManager.Reservations(v.user.Name)
if err != nil {
return err
}
if len(reservations) > 0 {
response.Reservations = make([]*apiAccountReservation, 0)
for _, r := range reservations {
var everyone string
if r.AllowEveryoneRead && r.AllowEveryoneWrite {
everyone = "read-write"
} else if r.AllowEveryoneRead && !r.AllowEveryoneWrite {
everyone = "read-only"
} else if !r.AllowEveryoneRead && r.AllowEveryoneWrite {
everyone = "write-only"
} else {
everyone = "deny-all"
}
response.Reservations = append(response.Reservations, &apiAccountReservation{
Topic: r.TopicPattern,
Everyone: everyone,
})
}
}
} else {
@ -356,9 +367,13 @@ func (s *Server) handleAccountAccessDelete(w http.ResponseWriter, r *http.Reques
if !topicRegex.MatchString(topic) {
return errHTTPBadRequestTopicInvalid
}
reservations, err := s.userManager.Reservations(v.user.Name) // FIXME replace with HasReservation
if err != nil {
return err
}
authorized := false
for _, grant := range v.user.Grants {
if grant.TopicPattern == topic && grant.Owner {
for _, r := range reservations {
if r.TopicPattern == topic {
authorized = true
break
}

View file

@ -326,7 +326,7 @@ func TestMaybeTruncateFCMMessage_NotTooLong(t *testing.T) {
func TestToFirebaseSender_Abuse(t *testing.T) {
sender := &testFirebaseSender{allowed: 2}
client := newFirebaseClient(sender, &testAuther{})
visitor := newVisitor(newTestConfig(t), newMemTestCache(t), netip.MustParseAddr("1.2.3.4"), nil)
visitor := newVisitor(newTestConfig(t), newMemTestCache(t), nil, netip.MustParseAddr("1.2.3.4"), nil)
require.Nil(t, client.Send(visitor, &message{Topic: "mytopic"}))
require.Equal(t, 1, len(sender.Messages()))

View file

@ -72,7 +72,7 @@ func TestMatrix_WriteMatrixDiscoveryResponse(t *testing.T) {
func TestMatrix_WriteMatrixError(t *testing.T) {
w := httptest.NewRecorder()
r, _ := http.NewRequest("POST", "http://ntfy.example.com/_matrix/push/v1/notify", nil)
v := newVisitor(newTestConfig(t), nil, netip.MustParseAddr("1.2.3.4"), nil)
v := newVisitor(newTestConfig(t), nil, nil, netip.MustParseAddr("1.2.3.4"), nil)
require.Nil(t, writeMatrixError(w, r, v, &errMatrix{"https://ntfy.example.com/upABCDEFGHI?up=1", errHTTPBadRequestMatrixPushkeyBaseURLMismatch}))
require.Equal(t, 200, w.Result().StatusCode)
require.Equal(t, `{"rejected":["https://ntfy.example.com/upABCDEFGHI?up=1"]}`+"\n", w.Body.String())

View file

@ -259,22 +259,21 @@ type apiAccountStats struct {
AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"`
}
type apiAccountGrant struct {
Topic string `json:"topic"`
Read bool `json:"read"`
Write bool `json:"write"`
type apiAccountReservation struct {
Topic string `json:"topic"`
Everyone string `json:"everyone"`
}
type apiAccountResponse struct {
Username string `json:"username"`
Role string `json:"role,omitempty"`
Language string `json:"language,omitempty"`
Notification *user.NotificationPrefs `json:"notification,omitempty"`
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
Access []*apiAccountGrant `json:"access,omitempty"`
Plan *apiAccountPlan `json:"plan,omitempty"`
Limits *apiAccountLimits `json:"limits,omitempty"`
Stats *apiAccountStats `json:"stats,omitempty"`
Username string `json:"username"`
Role string `json:"role,omitempty"`
Language string `json:"language,omitempty"`
Notification *user.NotificationPrefs `json:"notification,omitempty"`
Subscriptions []*user.Subscription `json:"subscriptions,omitempty"`
Reservations []*apiAccountReservation `json:"reservations,omitempty"`
Plan *apiAccountPlan `json:"plan,omitempty"`
Limits *apiAccountLimits `json:"limits,omitempty"`
Stats *apiAccountStats `json:"stats,omitempty"`
}
type apiAccountAccessRequest struct {

View file

@ -26,6 +26,7 @@ var (
type visitor struct {
config *Config
messageCache *messageCache
userManager *user.Manager // May be nil!
ip netip.Addr
user *user.User
messages int64 // Number of messages sent
@ -57,7 +58,7 @@ type visitorInfo struct {
AttachmentFileSizeLimit int64
}
func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *user.User) *visitor {
func newVisitor(conf *Config, messageCache *messageCache, userManager *user.Manager, ip netip.Addr, user *user.User) *visitor {
var requestLimiter, emailsLimiter, accountLimiter *rate.Limiter
var messages, emails int64
if user != nil {
@ -76,6 +77,7 @@ func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *u
return &visitor{
config: conf,
messageCache: messageCache,
userManager: userManager, // May be nil!
ip: ip,
user: user,
messages: messages,
@ -192,7 +194,7 @@ func (v *visitor) Info() (*visitorInfo, error) {
info.AttachmentTotalSizeLimit = v.config.VisitorAttachmentTotalSizeLimit
info.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
}
var attachmentsBytesUsed int64
var attachmentsBytesUsed int64 // FIXME Maybe move this to endpoint?
var err error
if v.user != nil {
attachmentsBytesUsed, err = v.messageCache.AttachmentBytesUsedByUser(v.user.Name)
@ -203,12 +205,12 @@ func (v *visitor) Info() (*visitorInfo, error) {
return nil, err
}
var topics int64
if v.user != nil {
for _, grant := range v.user.Grants {
if grant.Owner {
topics++
}
if v.user != nil && v.userManager != nil {
reservations, err := v.userManager.Reservations(v.user.Name) // FIXME dup call, move this to endpoint?
if err != nil {
return nil, err
}
topics = int64(len(reservations))
}
info.Messages = messages
info.MessagesRemaining = zeroIfNegative(info.MessagesLimit - info.Messages)