Commit 6014a12f authored by ale's avatar ale
Browse files

Add preliminary device tracking functionality

Still does not send out "new device" emails, but there are hooks to do
so.
parent db394674
......@@ -2,6 +2,7 @@ package server
import (
"context"
"errors"
"fmt"
"io/ioutil"
"log"
......@@ -13,6 +14,8 @@ import (
"github.com/tstranex/u2f"
"gopkg.in/yaml.v2"
usermetadbclient "git.autistici.org/id/usermetadb/client"
"git.autistici.org/id/auth"
)
......@@ -149,7 +152,9 @@ func (b *authBlacklist) Incr(user *User, req *auth.Request, resp *auth.Response)
b.bl.Incr(b.key(user, req))
}
type requestFilterFunc func(*User, *auth.Request, *auth.Response) *auth.Response
type requestFilter interface {
Filter(*User, *auth.Request, *auth.Response) *auth.Response
}
// BackendSpec specifies backend-specific configuration for a service.
type BackendSpec struct {
......@@ -159,15 +164,16 @@ type BackendSpec struct {
// ServiceConfig defines the authentication backends for a service.
type ServiceConfig struct {
BackendSpecs []*BackendSpec `yaml:"backends"`
ChallengeResponse bool `yaml:"challenge_response"`
Enforce2FA bool `yaml:"enforce_2fa"`
BackendSpecs []*BackendSpec `yaml:"backends"`
ChallengeResponse bool `yaml:"challenge_response"`
Enforce2FA bool `yaml:"enforce_2fa"`
EnableDeviceTracking bool `yaml:"enable_device_tracking"`
Ratelimits []string `yaml:"rate_limits"`
rl []*authRatelimiter
bl []*authBlacklist
filters []requestFilterFunc
filters []requestFilter
}
func (c *ServiceConfig) checkRateLimits(user *User, req *auth.Request) bool {
......@@ -204,6 +210,9 @@ type Config struct {
// Named rate limiter configurations.
RateLimiters map[string]*authRatelimiterConfig `yaml:"rate_limits"`
// Configuration for the user-meta-server backend.
UserMetaDBConfig *usermetadbclient.Config `yaml:"user_meta_server"`
// Runtime versions of the above. These objects are shared by
// all services, as they contain the actual map data.
rl map[string]*Ratelimiter
......@@ -231,6 +240,7 @@ func (c *Config) relativePath(path string) string {
}
func (c *Config) compile() error {
// Build the global rate limiters and blacklists.
c.rl = make(map[string]*Ratelimiter)
c.bl = make(map[string]*Blacklist)
for name, params := range c.RateLimiters {
......@@ -244,6 +254,7 @@ func (c *Config) compile() error {
}
}
// Compile each service definition.
for _, sc := range c.Services {
for _, name := range sc.Ratelimits {
config, ok := c.RateLimiters[name]
......@@ -258,6 +269,17 @@ func (c *Config) compile() error {
panic("can't find rl/bl")
}
}
if sc.EnableDeviceTracking {
if c.UserMetaDBConfig == nil {
return errors.New("usermetadb config is missing")
}
dt, err := newDeviceFilter(c.UserMetaDBConfig)
if err != nil {
return err
}
sc.filters = append(sc.filters, dt)
}
}
return nil
......@@ -396,7 +418,7 @@ func (s *Server) authenticateUser(req *auth.Request, serviceConfig *ServiceConfi
if resp.Status == auth.StatusError {
break
}
resp = f(user, req, resp)
resp = f.Filter(user, req, resp)
}
// If the response is successful, augment it with user information.
......
package server
import (
"context"
"log"
"time"
"git.autistici.org/id/usermetadb/client"
"git.autistici.org/id/auth"
)
// Inject an interface for testing purposes.
type userMetaDB interface {
CheckDevice(context.Context, string, *auth.DeviceInfo) (bool, error)
}
type deviceFilter struct {
client userMetaDB
}
var usermetadbTimeout = 3 * time.Second
func newDeviceFilter(config *client.Config) (*deviceFilter, error) {
tlsConfig, err := config.TLSConfig.TLSConfig()
if err != nil {
return nil, err
}
c, err := client.New(config.BackendURL, tlsConfig)
if err != nil {
return nil, err
}
return &deviceFilter{c}, nil
}
func (f *deviceFilter) Filter(user *User, req *auth.Request, resp *auth.Response) *auth.Response {
// If there is no DeviceInfo, skip.
if req.DeviceInfo == nil {
return resp
}
// Check if the device is known already, in which case we're
// OK and don't need to do anything else.
ctx, cancel := context.WithTimeout(context.Background(), usermetadbTimeout)
defer cancel()
seen, err := f.client.CheckDevice(ctx, user.Name, req.DeviceInfo)
if err != nil {
log.Printf("usermetadb error for %s: %v", user.Name, err)
return resp
}
if !seen {
// New device! Send out a warning and store it for the future.
// Errors are logged but non-fatal.
if err := f.sendNewDeviceEmail(user, req.DeviceInfo); err != nil {
log.Printf("error sending new device email to %s: %v", user.Name, err)
}
}
return resp
}
func (f *deviceFilter) sendNewDeviceEmail(user *User, dev *auth.DeviceInfo) error {
// TODO: Not implemented.
log.Printf("new device for user %s: %+v", user.Name, dev)
return nil
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment