Skip to content

Commit

Permalink
is: Add test for sending emails if admin
Browse files Browse the repository at this point in the history
  • Loading branch information
ryaplots committed Oct 30, 2024
1 parent 7a64e97 commit ed7391b
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 27 deletions.
5 changes: 3 additions & 2 deletions pkg/identityserver/bunstore/user_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func (s *userStore) CreateUser(ctx context.Context, pb *ttnpb.User) (*ttnpb.User
TemporaryPassword: pb.TemporaryPassword,
TemporaryPasswordCreatedAt: cleanTimePtr(ttnpb.StdTime(pb.TemporaryPasswordCreatedAt)),
TemporaryPasswordExpiresAt: cleanTimePtr(ttnpb.StdTime(pb.TemporaryPasswordExpiresAt)),
EmailNotificationPreferences: convertIntSlice[ttnpb.NotificationType, int](pb.EmailNotificationPreferences.GetTypes()),
EmailNotificationPreferences: convertIntSlice[ttnpb.NotificationType, int](pb.EmailNotificationPreferences.GetTypes()), // nolint:lll
}

if pb.ProfilePicture != nil {
Expand Down Expand Up @@ -607,7 +607,8 @@ func (s *userStore) updateUserModel( //nolint:gocyclo
case "universal_rights":
columns = append(columns, "universal_rights")
case "email_notification_preferences":
model.EmailNotificationPreferences = convertIntSlice[ttnpb.NotificationType, int](pb.EmailNotificationPreferences.Types)
case "email_notification_preferences.types":
model.EmailNotificationPreferences = convertIntSlice[ttnpb.NotificationType, int](pb.EmailNotificationPreferences.Types) // nolint:lll
columns = append(columns, "email_notification_preferences")
}
}
Expand Down
14 changes: 12 additions & 2 deletions pkg/identityserver/email.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,12 @@ func (is *IdentityServer) SendEmail(ctx context.Context, message *email.Message)
}

// SendTemplateEmailToUsers sends an email to users.
func (is *IdentityServer) SendTemplateEmailToUsers(ctx context.Context, templateName ttnpb.NotificationType, dataBuilder email.TemplateDataBuilder, receivers ...*ttnpb.User) error {
func (is *IdentityServer) SendTemplateEmailToUsers(
ctx context.Context,
templateName ttnpb.NotificationType,
dataBuilder email.TemplateDataBuilder,
receivers ...*ttnpb.User,
) error {
networkConfig := is.configFromContext(ctx).Email.Network
emailTemplate := email.GetTemplate(ctx, templateName)

Expand Down Expand Up @@ -118,7 +123,12 @@ func (is *IdentityServer) SendNotificationEmailToUsers(ctx context.Context, noti
var emailUserFields = store.FieldMask{"ids", "name", "primary_email_address"}

// SendTemplateEmailToUserIDs looks up the users and sends them an email.
func (is *IdentityServer) SendTemplateEmailToUserIDs(ctx context.Context, templateName ttnpb.NotificationType, dataBuilder email.TemplateDataBuilder, receiverIDs ...*ttnpb.UserIdentifiers) error {
func (is *IdentityServer) SendTemplateEmailToUserIDs(
ctx context.Context,
templateName ttnpb.NotificationType,
dataBuilder email.TemplateDataBuilder,
receiverIDs ...*ttnpb.UserIdentifiers,
) error {
var receivers []*ttnpb.User
err := is.store.Transact(ctx, func(ctx context.Context, st store.Store) (err error) {
receivers, err = st.FindUsers(ctx, receiverIDs, emailUserFields)
Expand Down
72 changes: 55 additions & 17 deletions pkg/identityserver/email_notification_preferences_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ package identityserver
import (
"os"
"testing"
"time"

"go.thethings.network/lorawan-stack/v3/pkg/identityserver/storetest"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
"go.thethings.network/lorawan-stack/v3/pkg/util/test"
"go.thethings.network/lorawan-stack/v3/pkg/util/test/assertions/should"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
)

func TestEmailNotificationPreferences(t *testing.T) {
Expand All @@ -31,36 +33,40 @@ func TestEmailNotificationPreferences(t *testing.T) {
admin := p.NewUser()
admin.Admin = true
adminKey, _ := p.NewAPIKey(admin.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL)
adminCreds := rpcCreds(adminKey)
admin.EmailNotificationPreferences = &ttnpb.EmailNotificationPreferences{
Types: []ttnpb.NotificationType{
ttnpb.NotificationType_API_KEY_CHANGED,
ttnpb.NotificationType_API_KEY_CREATED,
},
}
adminCreds := rpcCreds(adminKey)

usr1 := p.NewUser()
usr1.EmailNotificationPreferences = &ttnpb.EmailNotificationPreferences{
Types: []ttnpb.NotificationType{
ttnpb.NotificationType_API_KEY_CREATED,
ttnpb.NotificationType_API_KEY_CHANGED,
},
}
usr1Key, _ := p.NewAPIKey(usr1.GetEntityIdentifiers(), ttnpb.Right_RIGHT_ALL)
usr1Key, _ := p.NewAPIKey(usr1.GetEntityIdentifiers(),
ttnpb.Right_RIGHT_APPLICATION_INFO,
ttnpb.Right_RIGHT_APPLICATION_LINK,
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_API_KEYS,
)
usr1Creds := rpcCreds(usr1Key)

app1 := p.NewApplication(usr1.GetOrganizationOrUserIdentifiers())
limitedKey, _ := p.NewAPIKey(usr1.GetEntityIdentifiers(),
ttnpb.Right_RIGHT_APPLICATION_INFO,
app1 := p.NewApplication(admin.GetOrganizationOrUserIdentifiers())
p.NewMembership(
usr1.GetOrganizationOrUserIdentifiers(),
app1.GetEntityIdentifiers(),
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_BASIC,
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_API_KEYS,
ttnpb.Right_RIGHT_APPLICATION_LINK,
)
limitedCreds := rpcCreds(limitedKey)

appKey, _ := p.NewAPIKey(app1.GetEntityIdentifiers(),
ttnpb.Right_RIGHT_APPLICATION_INFO,
ttnpb.Right_RIGHT_APPLICATION_LINK,
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_API_KEYS,
)

now := timestamppb.Now()
usrIDs := &ttnpb.UserIdentifiers{
UserId: "foo-usr",
}

t.Parallel()
a, ctx := test.New(t)

Expand All @@ -71,31 +77,59 @@ func TestEmailNotificationPreferences(t *testing.T) {
is.config.Email.Dir = tempDir

reg := ttnpb.NewApplicationAccessClient(cc)
userReg := ttnpb.NewUserRegistryClient(cc)

// Test sending email to users that have API_KEY_CHANGED in their preferences.
// Test user not receiving email notification because this
// notification type is not in the list of preferences.
updated, err := reg.UpdateAPIKey(ctx, &ttnpb.UpdateApplicationAPIKeyRequest{
ApplicationIds: app1.GetIds(),
ApiKey: &ttnpb.APIKey{
Id: appKey.GetId(),
Rights: []ttnpb.Right{
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_BASIC,
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_API_KEYS,
ttnpb.Right_RIGHT_APPLICATION_LINK,
},
},
FieldMask: ttnpb.FieldMask("rights"),
}, limitedCreds)
}, adminCreds)
if a.So(err, should.BeNil) && a.So(updated, should.NotBeNil) {
a.So(updated.Rights, should.Resemble, []ttnpb.Right{
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_BASIC,
ttnpb.Right_RIGHT_APPLICATION_SETTINGS_API_KEYS,
ttnpb.Right_RIGHT_APPLICATION_LINK,
})
}

entries, err := os.ReadDir(tempDir)
a.So(err, should.BeNil)
a.So(entries, should.HaveLength, 0)

// Test admin receiving email notification in spite of the list of preferences.
updatedUser, err := userReg.Create(ctx, &ttnpb.CreateUserRequest{
User: &ttnpb.User{
Ids: usrIDs,
Password: "test password",
CreatedAt: now,
UpdatedAt: now,
Name: "Foo User",
Description: "Foo User Description",
PrimaryEmailAddress: "[email protected]",
State: ttnpb.State_STATE_REQUESTED,
},
}, adminCreds)
if a.So(err, should.BeNil) && a.So(updatedUser, should.NotBeNil) {
a.So(updatedUser.State, should.Equal, ttnpb.State_STATE_REQUESTED)
}

entries, err = os.ReadDir(tempDir)
a.So(err, should.BeNil)
a.So(entries, should.HaveLength, 1)

for _, opts := range [][]grpc.CallOption{{adminCreds}, {usr1Creds}, {limitedCreds}} {
time.Sleep(test.Delay)

// Test users receiving email notification because this notification type is in the list of preferences.
for _, opts := range [][]grpc.CallOption{{adminCreds}, {usr1Creds}} {
created, err := reg.CreateAPIKey(ctx, &ttnpb.CreateApplicationAPIKeyRequest{
ApplicationIds: app1.GetIds(),
Name: "api-key-name",
Expand All @@ -106,5 +140,9 @@ func TestEmailNotificationPreferences(t *testing.T) {
a.So(created.Rights, should.Resemble, []ttnpb.Right{ttnpb.Right_RIGHT_APPLICATION_INFO})
}
}

entries, err = os.ReadDir(tempDir)
a.So(err, should.BeNil)
a.So(entries, should.HaveLength, 3)
}, withPrivateTestDatabase(p))
}
10 changes: 4 additions & 6 deletions pkg/identityserver/notification_registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func receiversContains(receivers []ttnpb.NotificationReceiver, search ttnpb.Noti
return false
}

func notificationTypeAllowed(notificationType ttnpb.NotificationType, allowedNotifications []ttnpb.NotificationType) bool {
func notificationTypeAllowed(notificationType ttnpb.NotificationType, allowedNotifications []ttnpb.NotificationType) bool { //nolint:lll
for _, allowedType := range allowedNotifications {
if notificationType == allowedType {
return true
Expand All @@ -64,8 +64,9 @@ func notificationTypeAllowed(notificationType ttnpb.NotificationType, allowedNot
return false
}

func filterAllowedEmailReveivers(emailReceiverUsers []*ttnpb.User, notificationType ttnpb.NotificationType) []*ttnpb.UserIdentifiers {
func filterAllowedEmailReveivers(emailReceiverUsers []*ttnpb.User, notificationType ttnpb.NotificationType) []*ttnpb.UserIdentifiers { //nolint:lll
var emailReceiverIDs []*ttnpb.UserIdentifiers

// Collect IDs of users that have email notifications enabled for that notification type.
for _, user := range emailReceiverUsers {
userNotificationPreferences := user.GetEmailNotificationPreferences().GetTypes()
Expand Down Expand Up @@ -244,12 +245,10 @@ func (is *IdentityServer) lookupNotificationReceivers( //nolint:gocyclo

// Filter only user identifiers and remove duplicates.
receiverUserIDs = filterUserIdentifiers(uniqueOrganizationOrUserIdentifiers(ctx, receiverIDs))

// Get the email notification preferences of the receiver users.
emailReceiverUsers, _ := st.FindUsers(ctx, receiverUserIDs, []string{"email_notification_preferences"})
// Filter only the users that have email notifications enabled for the notification type.
emailReceiverIDs = filterAllowedEmailReveivers(emailReceiverUsers, req.NotificationType)

return nil
})
if err != nil {
Expand Down Expand Up @@ -292,7 +291,7 @@ func (is *IdentityServer) createNotification(ctx context.Context, req *ttnpb.Cre
}

if len(emailReceiverIDs) > 0 && email.GetNotification(ctx, req.GetNotificationType()) == nil {
log.FromContext(ctx).WithField("notification_type", req.GetNotificationType()).Warn("email template for notification not registered")
log.FromContext(ctx).WithField("notification_type", req.GetNotificationType()).Warn("email template for notification not registered") // nolint:lll
emailReceiverIDs = nil
}

Expand Down Expand Up @@ -333,7 +332,6 @@ func (is *IdentityServer) notifyAdminsInternal(ctx context.Context, req *ttnpb.C
if email.GetNotification(ctx, req.GetNotificationType()) == nil {
log.FromContext(ctx).WithField("notification_type", req.GetNotificationType()).Warn("email template for notification not registered")
}

var receivers []*ttnpb.User
err := is.store.Transact(ctx, func(ctx context.Context, st store.Store) (err error) {
receivers, err = st.ListAdmins(ctx, notificationEmailUserFields)
Expand Down

0 comments on commit ed7391b

Please sign in to comment.