mirror of
https://github.com/binwiederhier/ntfy.git
synced 2024-12-23 08:53:41 +03:00
implement scheduled message unpublishing via /topic/{messageId}
This commit is contained in:
parent
9d3fc20e58
commit
37468f38c2
@ -77,6 +77,7 @@ var (
|
|||||||
wsPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/ws$`)
|
wsPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/ws$`)
|
||||||
authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`)
|
authPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}(,[-_A-Za-z0-9]{1,64})*/auth$`)
|
||||||
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
|
publishPathRegex = regexp.MustCompile(`^/[-_A-Za-z0-9]{1,64}/(publish|send|trigger)$`)
|
||||||
|
messagePathRegex = regexp.MustCompile(`^/[-A-Za-z-0-9]{1,64}/([A-Za-z0-9]{12})$`)
|
||||||
|
|
||||||
webConfigPath = "/config.js"
|
webConfigPath = "/config.js"
|
||||||
webManifestPath = "/manifest.webmanifest"
|
webManifestPath = "/manifest.webmanifest"
|
||||||
@ -537,6 +538,8 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
|
|||||||
return s.limitRequests(s.authorizeTopicRead(s.handleTopicAuth))(w, r, v)
|
return s.limitRequests(s.authorizeTopicRead(s.handleTopicAuth))(w, r, v)
|
||||||
} else if r.Method == http.MethodGet && (topicPathRegex.MatchString(r.URL.Path) || externalTopicPathRegex.MatchString(r.URL.Path)) {
|
} else if r.Method == http.MethodGet && (topicPathRegex.MatchString(r.URL.Path) || externalTopicPathRegex.MatchString(r.URL.Path)) {
|
||||||
return s.ensureWebEnabled(s.handleTopic)(w, r, v)
|
return s.ensureWebEnabled(s.handleTopic)(w, r, v)
|
||||||
|
} else if r.Method == http.MethodDelete && (messagePathRegex.MatchString(r.URL.Path)) {
|
||||||
|
return s.limitRequestsWithTopic(s.authorizeTopicWrite(s.handleMessageUnpublish))(w, r, v)
|
||||||
}
|
}
|
||||||
return errHTTPNotFound
|
return errHTTPNotFound
|
||||||
}
|
}
|
||||||
@ -558,6 +561,25 @@ func (s *Server) handleTopic(w http.ResponseWriter, r *http.Request, v *visitor)
|
|||||||
return s.handleStatic(w, r, v)
|
return s.handleStatic(w, r, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) handleMessageUnpublish(w http.ResponseWriter, r *http.Request, v *visitor) error {
|
||||||
|
segments := strings.Split(r.URL.Path, "/")
|
||||||
|
msg, err := s.messageCache.Message(segments[len(segments)-1])
|
||||||
|
if err != nil {
|
||||||
|
if err == errMessageNotFound {
|
||||||
|
return errHTTPNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if time.Now().Unix() >= msg.Time {
|
||||||
|
httpErr := errHTTPBadRequest
|
||||||
|
alreadyPublished := httpErr.Wrap("Message \"%s\" has already been published", msg.ID)
|
||||||
|
s.handleError(w, r, v, alreadyPublished)
|
||||||
|
return alreadyPublished
|
||||||
|
}
|
||||||
|
s.messageCache.DeleteMessages(msg.ID)
|
||||||
|
return s.writeJSON(w, msg)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) handleEmpty(_ http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
func (s *Server) handleEmpty(_ http.ResponseWriter, _ *http.Request, _ *visitor) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
"heckel.io/ntfy/v2/user"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
@ -22,6 +20,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"heckel.io/ntfy/v2/user"
|
||||||
|
|
||||||
"github.com/SherClockHolmes/webpush-go"
|
"github.com/SherClockHolmes/webpush-go"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"heckel.io/ntfy/v2/log"
|
"heckel.io/ntfy/v2/log"
|
||||||
@ -2853,6 +2854,64 @@ template ""}}`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_Message_Unpublish_Existing(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
s := newTestServer(t, newTestConfig(t))
|
||||||
|
response1 := request(t, s, "PUT", "/mytopic", "hello universe", map[string]string{
|
||||||
|
"In": "1m",
|
||||||
|
})
|
||||||
|
msg1 := toMessage(t, response1.Body.String())
|
||||||
|
time.Sleep(500)
|
||||||
|
|
||||||
|
response2 := request(t, s, "DELETE", fmt.Sprintf("/mytopic/%s", msg1.ID), "", nil)
|
||||||
|
require.Equal(t, 200, response2.Code)
|
||||||
|
msg2 := toMessage(t, response2.Body.String())
|
||||||
|
require.Equal(t, msg1.ID, msg2.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_Message_Unpublish_Nonexistent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
s := newTestServer(t, newTestConfig(t))
|
||||||
|
|
||||||
|
response2 := request(t, s, "DELETE", "/mytopic/n0nexist3nt1", "", nil)
|
||||||
|
require.Equal(t, 404, response2.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_Message_Unpublish_Protected_Fail_Unauthorized(t *testing.T) {
|
||||||
|
c := newTestConfigWithAuthFile(t)
|
||||||
|
s := newTestServer(t, c)
|
||||||
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
|
||||||
|
require.Nil(t, s.userManager.AllowAccess(user.Everyone, "announcements", user.PermissionRead))
|
||||||
|
|
||||||
|
response1 := request(t, s, "PUT", "/announcements", "hello universe", map[string]string{
|
||||||
|
"In": "1m",
|
||||||
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
})
|
||||||
|
msg1 := toMessage(t, response1.Body.String())
|
||||||
|
|
||||||
|
response2 := request(t, s, "DELETE", fmt.Sprintf("/announcements/%s", msg1.ID), "", nil)
|
||||||
|
require.Equal(t, 403, response2.Code)
|
||||||
|
}
|
||||||
|
func TestServer_Message_Unpublish_Private(t *testing.T) {
|
||||||
|
c := newTestConfigWithAuthFile(t)
|
||||||
|
c.AuthDefault = user.PermissionDenyAll
|
||||||
|
s := newTestServer(t, c)
|
||||||
|
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
|
||||||
|
require.Nil(t, s.userManager.AllowAccess("phil", "announcements", user.PermissionReadWrite))
|
||||||
|
|
||||||
|
h := map[string]string{
|
||||||
|
"In": "1m",
|
||||||
|
"Authorization": util.BasicAuth("phil", "phil"),
|
||||||
|
}
|
||||||
|
response1 := request(t, s, "PUT", "/announcements", "hello universe", h)
|
||||||
|
msg1 := toMessage(t, response1.Body.String())
|
||||||
|
|
||||||
|
response2 := request(t, s, "DELETE", fmt.Sprintf("/announcements/%s", msg1.ID), "", h)
|
||||||
|
msg2 := toMessage(t, response2.Body.String())
|
||||||
|
require.Equal(t, 200, response2.Code)
|
||||||
|
require.Equal(t, msg1.ID, msg2.ID)
|
||||||
|
}
|
||||||
|
|
||||||
func newTestConfig(t *testing.T) *Config {
|
func newTestConfig(t *testing.T) *Config {
|
||||||
conf := NewConfig()
|
conf := NewConfig()
|
||||||
conf.BaseURL = "http://127.0.0.1:12345"
|
conf.BaseURL = "http://127.0.0.1:12345"
|
||||||
|
Loading…
Reference in New Issue
Block a user