Payments webhook test, delete attachments/messages when reservations are removed,
This commit is contained in:
parent
45b97c7054
commit
31a3bb7cd6
16 changed files with 571 additions and 157 deletions
107
user/manager.go
107
user/manager.go
|
@ -219,8 +219,7 @@ const (
|
|||
INSERT INTO tier (code, name, messages_limit, messages_expiry_duration, emails_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration, stripe_price_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
selectTierIDQuery = `SELECT id FROM tier WHERE code = ?`
|
||||
selectTiersQuery = `
|
||||
selectTiersQuery = `
|
||||
SELECT code, name, messages_limit, messages_expiry_duration, emails_limit, reservations_limit, attachment_file_size_limit, attachment_total_size_limit, attachment_expiry_duration, stripe_price_id
|
||||
FROM tier
|
||||
`
|
||||
|
@ -234,7 +233,7 @@ const (
|
|||
FROM tier
|
||||
WHERE stripe_price_id = ?
|
||||
`
|
||||
updateUserTierQuery = `UPDATE user SET tier_id = ? WHERE user = ?`
|
||||
updateUserTierQuery = `UPDATE user SET tier_id = (SELECT id FROM tier WHERE code = ?) WHERE user = ?`
|
||||
deleteUserTierQuery = `UPDATE user SET tier_id = null WHERE user = ?`
|
||||
|
||||
updateBillingQuery = `
|
||||
|
@ -772,26 +771,47 @@ func (a *Manager) ChangeRole(username string, role Role) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ChangeTier changes a user's tier using the tier code
|
||||
// ChangeTier changes a user's tier using the tier code. This function does not delete reservations, messages,
|
||||
// or attachments, even if the new tier has lower limits in this regard. That has to be done elsewhere.
|
||||
func (a *Manager) ChangeTier(username, tier string) error {
|
||||
if !AllowedUsername(username) {
|
||||
return ErrInvalidArgument
|
||||
}
|
||||
rows, err := a.db.Query(selectTierIDQuery, tier)
|
||||
t, err := a.Tier(tier)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if err := a.checkReservationsLimit(username, t.ReservationsLimit); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := a.db.Exec(updateUserTierQuery, tier, username); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetTier removes the tier from the given user
|
||||
func (a *Manager) ResetTier(username string) error {
|
||||
if !AllowedUsername(username) && username != Everyone && username != "" {
|
||||
return ErrInvalidArgument
|
||||
} else if err := a.checkReservationsLimit(username, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := a.db.Exec(deleteUserTierQuery, username)
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *Manager) checkReservationsLimit(username string, reservationsLimit int64) error {
|
||||
u, err := a.User(username)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
if !rows.Next() {
|
||||
return ErrInvalidArgument
|
||||
}
|
||||
var tierID int64
|
||||
if err := rows.Scan(&tierID); err != nil {
|
||||
return err
|
||||
}
|
||||
rows.Close()
|
||||
if _, err := a.db.Exec(updateUserTierQuery, tierID, username); err != nil {
|
||||
return err
|
||||
if u.Tier != nil && reservationsLimit < u.Tier.ReservationsLimit {
|
||||
reservations, err := a.Reservations(username)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if int64(len(reservations)) > reservationsLimit {
|
||||
return ErrTooManyReservations
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -823,20 +843,37 @@ func (a *Manager) CheckAllowAccess(username string, topic string) error {
|
|||
// AllowAccess adds or updates an entry in th access control list for a specific user. It controls
|
||||
// read/write access to a topic. The parameter topicPattern may include wildcards (*). The ACL entry
|
||||
// owner may either be a user (username), or the system (empty).
|
||||
func (a *Manager) AllowAccess(owner, username string, topicPattern string, read bool, write bool) error {
|
||||
func (a *Manager) AllowAccess(username string, topicPattern string, permission Permission) error {
|
||||
if !AllowedUsername(username) && username != Everyone {
|
||||
return ErrInvalidArgument
|
||||
} else if owner != "" && !AllowedUsername(owner) {
|
||||
return ErrInvalidArgument
|
||||
} else if !AllowedTopicPattern(topicPattern) {
|
||||
return ErrInvalidArgument
|
||||
}
|
||||
if _, err := a.db.Exec(upsertUserAccessQuery, username, toSQLWildcard(topicPattern), read, write, owner, owner); err != nil {
|
||||
owner := ""
|
||||
if _, err := a.db.Exec(upsertUserAccessQuery, username, toSQLWildcard(topicPattern), permission.IsRead(), permission.IsWrite(), owner, owner); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Manager) ReserveAccess(username string, topic string, everyone Permission) error {
|
||||
if !AllowedUsername(username) || username == Everyone || !AllowedTopic(topic) {
|
||||
return ErrInvalidArgument
|
||||
}
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err := tx.Exec(upsertUserAccessQuery, username, topic, true, true, username, username); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(upsertUserAccessQuery, Everyone, topic, everyone.IsRead(), everyone.IsWrite(), username, username); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// ResetAccess removes an access control list entry for a specific username/topic, or (if topic is
|
||||
// empty) for an entire user. The parameter topicPattern may include wildcards (*).
|
||||
func (a *Manager) ResetAccess(username string, topicPattern string) error {
|
||||
|
@ -856,13 +893,29 @@ func (a *Manager) ResetAccess(username string, topicPattern string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// ResetTier removes the tier from the given user
|
||||
func (a *Manager) ResetTier(username string) error {
|
||||
if !AllowedUsername(username) && username != Everyone && username != "" {
|
||||
func (a *Manager) RemoveReservations(username string, topics ...string) error {
|
||||
if !AllowedUsername(username) || username == Everyone || len(topics) == 0 {
|
||||
return ErrInvalidArgument
|
||||
}
|
||||
_, err := a.db.Exec(deleteUserTierQuery, username)
|
||||
return err
|
||||
for _, topic := range topics {
|
||||
if !AllowedTopic(topic) {
|
||||
return ErrInvalidArgument
|
||||
}
|
||||
}
|
||||
tx, err := a.db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
for _, topic := range topics {
|
||||
if _, err := tx.Exec(deleteTopicAccessQuery, username, username, topic); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(deleteTopicAccessQuery, Everyone, Everyone, topic); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// DefaultAccess returns the default read/write access if no access control entry matches
|
||||
|
@ -879,8 +932,8 @@ func (a *Manager) CreateTier(tier *Tier) error {
|
|||
}
|
||||
|
||||
// ChangeBilling updates a user's billing fields, namely the Stripe customer ID, and subscription information
|
||||
func (a *Manager) ChangeBilling(user *User) error {
|
||||
if _, err := a.db.Exec(updateBillingQuery, nullString(user.Billing.StripeCustomerID), nullString(user.Billing.StripeSubscriptionID), nullString(string(user.Billing.StripeSubscriptionStatus)), nullInt64(user.Billing.StripeSubscriptionPaidUntil.Unix()), nullInt64(user.Billing.StripeSubscriptionCancelAt.Unix()), user.Name); err != nil {
|
||||
func (a *Manager) ChangeBilling(username string, billing *Billing) error {
|
||||
if _, err := a.db.Exec(updateBillingQuery, nullString(billing.StripeCustomerID), nullString(billing.StripeSubscriptionID), nullString(string(billing.StripeSubscriptionStatus)), nullInt64(billing.StripeSubscriptionPaidUntil.Unix()), nullInt64(billing.StripeSubscriptionCancelAt.Unix()), username); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -15,13 +15,13 @@ func TestManager_FullScenario_Default_DenyAll(t *testing.T) {
|
|||
a := newTestManager(t, PermissionDenyAll)
|
||||
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "everyonewrite", false, false)) // How unfair!
|
||||
require.Nil(t, a.AllowAccess("", Everyone, "announcements", true, false))
|
||||
require.Nil(t, a.AllowAccess("", Everyone, "everyonewrite", true, true))
|
||||
require.Nil(t, a.AllowAccess("", Everyone, "up*", false, true)) // Everyone can write to /up*
|
||||
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair!
|
||||
require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess(Everyone, "up*", PermissionWrite)) // Everyone can write to /up*
|
||||
|
||||
phil, err := a.Authenticate("phil", "phil")
|
||||
require.Nil(t, err)
|
||||
|
@ -130,12 +130,12 @@ func TestManager_UserManagement(t *testing.T) {
|
|||
a := newTestManager(t, PermissionDenyAll)
|
||||
require.Nil(t, a.AddUser("phil", "phil", RoleAdmin, "unit-test"))
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "everyonewrite", false, false)) // How unfair!
|
||||
require.Nil(t, a.AllowAccess("", Everyone, "announcements", true, false))
|
||||
require.Nil(t, a.AllowAccess("", Everyone, "everyonewrite", true, true))
|
||||
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "everyonewrite", PermissionDenyAll)) // How unfair!
|
||||
require.Nil(t, a.AllowAccess(Everyone, "announcements", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess(Everyone, "everyonewrite", PermissionReadWrite))
|
||||
|
||||
// Query user details
|
||||
phil, err := a.User("phil")
|
||||
|
@ -177,9 +177,9 @@ func TestManager_UserManagement(t *testing.T) {
|
|||
}, everyoneGrants)
|
||||
|
||||
// Ben: Before revoking
|
||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true)) // Overwrite!
|
||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "writeme", false, true))
|
||||
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite)) // Overwrite!
|
||||
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("ben", "writeme", PermissionWrite))
|
||||
require.Nil(t, a.Authorize(ben, "mytopic", PermissionRead))
|
||||
require.Nil(t, a.Authorize(ben, "mytopic", PermissionWrite))
|
||||
require.Nil(t, a.Authorize(ben, "readme", PermissionRead))
|
||||
|
@ -234,8 +234,8 @@ func TestManager_ChangePassword(t *testing.T) {
|
|||
func TestManager_ChangeRole(t *testing.T) {
|
||||
a := newTestManager(t, PermissionDenyAll)
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "mytopic", true, true))
|
||||
require.Nil(t, a.AllowAccess("", "ben", "readme", true, false))
|
||||
require.Nil(t, a.AllowAccess("ben", "mytopic", PermissionReadWrite))
|
||||
require.Nil(t, a.AllowAccess("ben", "readme", PermissionRead))
|
||||
|
||||
ben, err := a.User("ben")
|
||||
require.Nil(t, err)
|
||||
|
@ -256,6 +256,28 @@ func TestManager_ChangeRole(t *testing.T) {
|
|||
require.Equal(t, 0, len(benGrants))
|
||||
}
|
||||
|
||||
func TestManager_Reservations(t *testing.T) {
|
||||
a := newTestManager(t, PermissionDenyAll)
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||
require.Nil(t, a.ReserveAccess("ben", "ztopic", PermissionDenyAll))
|
||||
require.Nil(t, a.ReserveAccess("ben", "readme", PermissionRead))
|
||||
require.Nil(t, a.AllowAccess("ben", "something-else", PermissionRead))
|
||||
|
||||
reservations, err := a.Reservations("ben")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 2, len(reservations))
|
||||
require.Equal(t, Reservation{
|
||||
Topic: "readme",
|
||||
Owner: PermissionReadWrite,
|
||||
Everyone: PermissionRead,
|
||||
}, reservations[0])
|
||||
require.Equal(t, Reservation{
|
||||
Topic: "ztopic",
|
||||
Owner: PermissionReadWrite,
|
||||
Everyone: PermissionDenyAll,
|
||||
}, reservations[1])
|
||||
}
|
||||
|
||||
func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
|
||||
a := newTestManager(t, PermissionDenyAll)
|
||||
require.Nil(t, a.CreateTier(&Tier{
|
||||
|
@ -272,8 +294,7 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
|
|||
}))
|
||||
require.Nil(t, a.AddUser("ben", "ben", RoleUser, "unit-test"))
|
||||
require.Nil(t, a.ChangeTier("ben", "pro"))
|
||||
require.Nil(t, a.AllowAccess("ben", "ben", "mytopic", true, true))
|
||||
require.Nil(t, a.AllowAccess("ben", Everyone, "mytopic", false, false))
|
||||
require.Nil(t, a.ReserveAccess("ben", "mytopic", PermissionDenyAll))
|
||||
|
||||
ben, err := a.User("ben")
|
||||
require.Nil(t, err)
|
||||
|
@ -298,6 +319,13 @@ func TestManager_ChangeRoleFromTierUserToAdmin(t *testing.T) {
|
|||
require.Equal(t, 1, len(everyoneGrants))
|
||||
require.Equal(t, PermissionDenyAll, everyoneGrants[0].Allow)
|
||||
|
||||
benReservations, err := a.Reservations("ben")
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, 1, len(benReservations))
|
||||
require.Equal(t, "mytopic", benReservations[0].Topic)
|
||||
require.Equal(t, PermissionReadWrite, benReservations[0].Owner)
|
||||
require.Equal(t, PermissionDenyAll, benReservations[0].Everyone)
|
||||
|
||||
// Switch to admin, this should remove all grants and owned ACL entries
|
||||
require.Nil(t, a.ChangeRole("ben", RoleAdmin))
|
||||
|
||||
|
|
|
@ -221,19 +221,10 @@ func AllowedTier(tier string) bool {
|
|||
|
||||
// Error constants used by the package
|
||||
var (
|
||||
ErrUnauthenticated = errors.New("unauthenticated")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrTierNotFound = errors.New("tier not found")
|
||||
)
|
||||
|
||||
// BillingStatus represents the status of a Stripe subscription
|
||||
type BillingStatus string
|
||||
|
||||
// BillingStatus values, subset of https://stripe.com/docs/billing/subscriptions/overview
|
||||
const (
|
||||
BillingStatusIncomplete = BillingStatus("incomplete")
|
||||
BillingStatusActive = BillingStatus("active")
|
||||
BillingStatusPastDue = BillingStatus("past_due")
|
||||
ErrUnauthenticated = errors.New("unauthenticated")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
ErrInvalidArgument = errors.New("invalid argument")
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
ErrTierNotFound = errors.New("tier not found")
|
||||
ErrTooManyReservations = errors.New("new tier has lower reservation limit")
|
||||
)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue