Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Commit

Permalink
use redirect hostname to lookup realm for correct mobile app (#782)
Browse files Browse the repository at this point in the history
* use redirect hostname to lookup realm for correct mobile app

* spelling

* review comments
  • Loading branch information
mikehelmick authored Oct 8, 2020
1 parent 4f9bd3b commit 44303f9
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 32 deletions.
2 changes: 1 addition & 1 deletion cmd/enx-redirect/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func realMain(ctx context.Context) error {
wk := r.PathPrefix("/.well-known").Subrouter()

// Enable the iOS and Android redirect handler.
assocHandler, err := associated.New(ctx, db, cacher, h)
assocHandler, err := associated.New(ctx, cfg, db, cacher, h)
if err != nil {
return fmt.Errorf("failed to create associated links handler %w", err)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/config/redirect_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"fmt"
"strings"
"time"

"github.com/google/exposure-notifications-server/pkg/observability"
"github.com/google/exposure-notifications-verification-server/pkg/cache"
Expand All @@ -36,6 +37,8 @@ type RedirectConfig struct {

AssetsPath string `env:"ASSETS_PATH, default=./cmd/enx-redirect/assets"`

AppCacheTTL time.Duration `env:"APP_CACHE_TTL, default=5m"`

// If Dev mode is true, extended logging is enabled and template
// auto-reload is enabled.
DevMode bool `env:"DEV_MODE"`
Expand Down
9 changes: 7 additions & 2 deletions pkg/controller/associated/android.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,13 @@ type Target struct {
}

// getAndroidData finds all the android data apps.
func (c *Controller) getAndroidData() ([]AndroidData, error) {
apps, err := c.db.ListActiveAppsByOS(database.OSTypeAndroid)
func (c *Controller) getAndroidData(region string) ([]AndroidData, error) {
realm, err := c.db.FindRealmByRegion(region)
if err != nil {
return nil, fmt.Errorf("unable to lookup realm")
}

apps, err := c.db.ListActiveAppsByOS(realm.ID, database.OSTypeAndroid)
if err != nil {
return nil, fmt.Errorf("failed to get android data: %w", err)
}
Expand Down
80 changes: 61 additions & 19 deletions pkg/controller/associated/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,70 +23,112 @@ package associated

import (
"context"
"fmt"
"net/http"
"time"
"strings"

"github.com/google/exposure-notifications-server/pkg/logging"
"github.com/google/exposure-notifications-verification-server/pkg/api"
"github.com/google/exposure-notifications-verification-server/pkg/cache"
"github.com/google/exposure-notifications-verification-server/pkg/config"
"github.com/google/exposure-notifications-verification-server/pkg/controller"
"github.com/google/exposure-notifications-verification-server/pkg/database"
"github.com/google/exposure-notifications-verification-server/pkg/render"
"go.uber.org/zap"
)

type Controller struct {
cacher cache.Cacher
db *database.Database
h *render.Renderer
logger *zap.SugaredLogger
config *config.RedirectConfig
hostnameToRegion map[string]string
cacher cache.Cacher
db *database.Database
h *render.Renderer
logger *zap.SugaredLogger
}

// cacheTTL is the amount of time for which to cache these values.
const cacheTTL = 30 * time.Minute
func (c *Controller) getRegion(r *http.Request) string {
// Get the hostname first
host := strings.ToLower(r.Host)
if i := strings.Index(host, ":"); i > 0 {
host = host[0:i]
}

// return the mapped region code (or default, "", if not found)
return c.hostnameToRegion[host]
}

func (c *Controller) HandleIos() http.Handler {
notFound := api.Errorf("not found")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
cacheKey := r.URL.String()
region := c.getRegion(r)
if region == "" {
c.h.RenderJSON(w, http.StatusNotFound, notFound)
return
}

var iosData IOSData
if err := c.cacher.Fetch(ctx, cacheKey, &iosData, cacheTTL, func() (interface{}, error) {
cacheKey := fmt.Sprintf("iosapps:by_region:%s", region)
var iosData *IOSData
if err := c.cacher.Fetch(ctx, cacheKey, &iosData, c.config.AppCacheTTL, func() (interface{}, error) {
c.logger.Debug("fetching new ios data")
return c.getIosData()
return c.getIosData(region)
}); err != nil {
controller.InternalError(w, r, c.h, err)
return
}

if iosData == nil {
c.h.RenderJSON(w, http.StatusNotFound, notFound)
return
}

c.h.RenderJSON(w, http.StatusOK, iosData)
})
}

func (c *Controller) HandleAndroid() http.Handler {
notFound := api.Errorf("not found")
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
cacheKey := r.URL.String()
region := c.getRegion(r)
if region == "" {
c.h.RenderJSON(w, http.StatusNotFound, notFound)
return
}

cacheKey := fmt.Sprintf("androidapps:by_region:%s", region)
var androidData []AndroidData
if err := c.cacher.Fetch(ctx, cacheKey, &androidData, cacheTTL, func() (interface{}, error) {
if err := c.cacher.Fetch(ctx, cacheKey, &androidData, c.config.AppCacheTTL, func() (interface{}, error) {
c.logger.Debug("fetching new android data")
return c.getAndroidData()
return c.getAndroidData(region)
}); err != nil {
controller.InternalError(w, r, c.h, err)
return
}

if len(androidData) == 0 {
c.h.RenderJSON(w, http.StatusNotFound, notFound)
return
}

c.h.RenderJSON(w, http.StatusOK, androidData)
})
}

func New(ctx context.Context, db *database.Database, cacher cache.Cacher, h *render.Renderer) (*Controller, error) {
func New(ctx context.Context, config *config.RedirectConfig, db *database.Database, cacher cache.Cacher, h *render.Renderer) (*Controller, error) {
logger := logging.FromContext(ctx).Named("associated")

cfgMap, err := config.HostnameToRegion()
if err != nil {
return nil, fmt.Errorf("invalid config: %w", err)
}

return &Controller{
db: db,
cacher: cacher,
h: h,
logger: logger,
config: config,
db: db,
cacher: cacher,
h: h,
logger: logger,
hostnameToRegion: cfgMap,
}, nil
}
24 changes: 17 additions & 7 deletions pkg/controller/associated/ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ package associated
// The iOS format is specified by:
// https://developer.apple.com/documentation/safariservices/supporting_associated_domains

import "github.com/google/exposure-notifications-verification-server/pkg/database"
import (
"fmt"

"github.com/google/exposure-notifications-verification-server/pkg/database"
)

type IOSData struct {
Applinks Applinks `json:"applinks"`
Expand Down Expand Up @@ -49,8 +53,8 @@ type Appstrings struct {
}

// getAppIds finds all the iOS app ids we know about.
func (c *Controller) getAppIds() ([]string, error) {
apps, err := c.db.ListActiveAppsByOS(database.OSTypeIOS)
func (c *Controller) getAppIds(realmID uint) ([]string, error) {
apps, err := c.db.ListActiveAppsByOS(realmID, database.OSTypeIOS)
if err != nil {
return nil, err
}
Expand All @@ -62,15 +66,21 @@ func (c *Controller) getAppIds() ([]string, error) {
}

// getIosData gets the iOS app data.
func (c *Controller) getIosData() (*IOSData, error) {
var ids []string
var err error
func (c *Controller) getIosData(region string) (*IOSData, error) {
realm, err := c.db.FindRealmByRegion(region)
if err != nil {
return nil, fmt.Errorf("unable to lookup realm")
}

ids, err = c.getAppIds()
ids, err := c.getAppIds(realm.ID)
if err != nil {
return nil, err
}

if len(ids) == 0 {
return nil, nil
}

return &IOSData{
Applinks: Applinks{
Details: []Detail{{
Expand Down
6 changes: 3 additions & 3 deletions pkg/database/mobile_app.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,13 @@ func (a *MobileApp) BeforeSave(tx *gorm.DB) error {
return nil
}

// ListActiveAppsByOS finds all mobile apps by their OS.
func (db *Database) ListActiveAppsByOS(os OSType) ([]*MobileApp, error) {
// ListActiveAppsByOS finds mobile apps by their realm and OS.
func (db *Database) ListActiveAppsByOS(realmID uint, os OSType) ([]*MobileApp, error) {
// Find the apps.
var apps []*MobileApp
if err := db.db.
Model(&MobileApp{}).
Where("os = ?", os).
Where("realm_id = ? AND os = ?", realmID, os).
Find(&apps).
Error; err != nil {
if IsNotFound(err) {
Expand Down
9 changes: 9 additions & 0 deletions pkg/database/realm.go
Original file line number Diff line number Diff line change
Expand Up @@ -716,6 +716,15 @@ func (db *Database) CreateRealm(name string) (*Realm, error) {
return realm, nil
}

func (db *Database) FindRealmByRegion(region string) (*Realm, error) {
var realm Realm

if err := db.db.Where("region_code = ?", strings.ToUpper(region)).First(&realm).Error; err != nil {
return nil, err
}
return &realm, nil
}

func (db *Database) FindRealmByName(name string) (*Realm, error) {
var realm Realm

Expand Down

0 comments on commit 44303f9

Please sign in to comment.