Skip to content

Commit

Permalink
converting hard-coded prefixes to calls to validators and authenticators
Browse files Browse the repository at this point in the history
  • Loading branch information
or-else committed Jul 10, 2020
1 parent 21b6d71 commit 0897120
Show file tree
Hide file tree
Showing 14 changed files with 457 additions and 355 deletions.
441 changes: 221 additions & 220 deletions pbx/model.pb.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions pbx/model.proto
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ message ClientCred {
string value = 2;
// Verification response
string response = 3;
// Request parameters, such as preferences.
bytes params = 4;
// Request parameters, such as preferences or country code.
map<string, bytes> params = 4;
}

// SetDesc: C2S in set.what == "desc" and sub.init message
Expand Down
244 changes: 146 additions & 98 deletions py_grpc/tinode_grpc/model_pb2.py

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions rest-auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ def upd():

@app.route('/rtagns', methods=['POST'])
def rtags():
# Return dummy namespace "rest" and "email"
return jsonify({'strarr': ['rest', 'email']})
# Return dummy namespace "rest" and "email", let client check logins by regular expression.
return jsonify({'strarr': ['rest', 'email'], 'byteval': base64.b64encode('^[a-z0-9_]{3,8}$')})

@app.errorhandler(404)
def not_found(error):
Expand Down
5 changes: 5 additions & 0 deletions server/auth/anon/auth_anon.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ func (authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
return nil, nil, types.ErrUnsupported
}

// AsTag is not supported, will produce an empty string.
func (authenticator) AsTag(token string) string {
return ""
}

// IsUnique for a noop. Anonymous login does not use secret, any secret is fine.
func (authenticator) IsUnique(secret []byte) (bool, error) {
return true, nil
Expand Down
4 changes: 4 additions & 0 deletions server/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ type AuthHandler interface {
// Returns: user auth record, challenge, error.
Authenticate(secret []byte) (*Rec, []byte, error)

// AsTag converts search token into prefixed tag or an empty string if it
// cannot be represented as a prefixed tag.
AsTag(token string) string

// IsUnique verifies if the provided secret can be considered unique by the auth scheme
// E.g. if login is unique.
IsUnique(secret []byte) (bool, error)
Expand Down
20 changes: 13 additions & 7 deletions server/auth/basic/auth_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,17 @@ func (a *authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
State: types.StateUndefined}, nil, nil
}

func (a *authenticator) PreCheck(secret []byte) (string, error) {
if err := a.checkLoginPolicy(uname); err != nil {
return "", err
// AsTag convert search token into a prefixed tag, if possible.
func (a *authenticator) AsTag(token string) string {
if !a.addToTags {
return ""
}

if err := a.checkLoginPolicy(token); err != nil {
return ""
}
return a.name + ":" + uname

return a.name + ":" + token
}

// IsUnique checks login uniqueness.
Expand Down Expand Up @@ -280,11 +286,11 @@ func (a *authenticator) DelRecords(uid types.Uid) error {

// RestrictedTags returns tag namespaces (prefixes) restricted by this adapter.
func (a *authenticator) RestrictedTags() ([]string, error) {
var tags []string
var prefix []string
if a.addToTags {
tags = []string{a.name}
prefix = []string{a.name}
}
return tags, nil
return prefix, nil
}

// GetResetParams returns authenticator parameters passed to password reset handler.
Expand Down
7 changes: 5 additions & 2 deletions server/auth/rest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,9 @@ If accounts are managed by the server, the server should respond with an error `

### `rtagns` Get a list of restricted tag namespaces.

Server may enforce certain tag namespaces to be restricted, i.e. not editable by the user.
Server may enforce certain tag namespaces (tag prefixes) to be restricted, i.e. not editable by the user.
These are also used when searching for users.
The server may optionally provide a regular expression to validate search tokens before rewriting them as prefixed tags. I.e. if server allows only logins of 3-8 ASCII letters and numbers then the regexp could be `^[a-z0-9_]{3,8}$` which is base64-encoded as `XlthLXowLTlfXXszLDh9JA==`.

#### Sample request
```json
Expand All @@ -333,6 +335,7 @@ Server may enforce certain tag namespaces to be restricted, i.e. not editable by
#### Sample response
```json
{
"strarr": ["basic", "email", "tel"]
"strarr": ["basic", "email", "tel"],
"byteval": "XlthLXowLTlfXXszLDh9JA=="
}
```
32 changes: 32 additions & 0 deletions server/auth/rest/auth_rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"encoding/json"
"errors"
"io/ioutil"
"log"
"net/http"
"net/url"
"regexp"
"strings"
"time"

Expand All @@ -26,6 +28,10 @@ type authenticator struct {
allowNewAccounts bool
// Use separate endpoints, i.e. add request name to serverUrl path when making requests.
useSeparateEndpoints bool
// Cache of restricted tag prefixes (namespaces).
rTagNS []string
// Optional regex pattern for checking tokens.
reToken *regexp.Regexp
}

// Request to the server.
Expand Down Expand Up @@ -202,6 +208,19 @@ func (a *authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
return resp.Record, resp.ByteVal, nil
}

// AsTag converts search token into prefixed tag or an empty string if it
// cannot be represented as a prefixed tag.
func (a *authenticator) AsTag(token string) string {
if len(a.rTagNS) > 0 {
if a.reToken != nil && !a.reToken.MatchString(token) {
return ""
}
// No validation or passed validation.
return a.rTagNS[0] + ":" + token
}
return ""
}

// IsUnique verifies if the provided secret can be considered unique by the auth scheme
// E.g. if login is unique.
func (a *authenticator) IsUnique(secret []byte) (bool, error) {
Expand Down Expand Up @@ -231,11 +250,24 @@ func (a *authenticator) DelRecords(uid types.Uid) error {

// RestrictedTags returns tag namespaces (prefixes, such as prefix:login) restricted by the server.
func (a *authenticator) RestrictedTags() ([]string, error) {
if a.rTagNS != nil {
ns := make([]string, len(a.rTagNS))
copy(ns, a.rTagNS)
return ns, nil
}
resp, err := a.callEndpoint("rtagns", nil, nil)
if err != nil {
return nil, err
}

// Save valid result to cache.
a.rTagNS = resp.StrSliceVal
if len(resp.ByteVal) > 0 {
a.reToken, err = regexp.Compile(string(resp.ByteVal))
if err != nil {
log.Println("rest_auth: invalid token regexp", string(resp.ByteVal))
}
}
return resp.StrSliceVal, nil
}

Expand Down
5 changes: 5 additions & 0 deletions server/auth/token/auth_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ func (ta *authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, error) {
return buf.Bytes(), expires, nil
}

// AsTag is not supported, will produce an empty string.
func (authenticator) AsTag(token string) string {
return ""
}

// IsUnique is not supported, will produce an error.
func (authenticator) IsUnique(token []byte) (bool, error) {
return false, types.ErrUnsupported
Expand Down
2 changes: 1 addition & 1 deletion server/datamodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ type MsgCredClient struct {
// Verification response
Response string `json:"resp,omitempty"`
// Request parameters, such as preferences. Passed to valiator without interpretation.
Params interface{} `json:"params,omitempty"`
Params map[string]interface{} `json:"params,omitempty"`
}

// MsgSetQuery is an update to topic metadata: Desc, subscriptions, or tags.
Expand Down
4 changes: 2 additions & 2 deletions server/pbconverter.go
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,7 @@ func pbClientCredSerialize(in *MsgCredClient) *pbx.ClientCred {
Method: in.Method,
Value: in.Value,
Response: in.Response,
Params: interfaceToBytes(in.Params)}
Params: interfaceMapToByteMap(in.Params)}

}

Expand All @@ -947,7 +947,7 @@ func pbClientCredDeserialize(in *pbx.ClientCred) *MsgCredClient {
Method: in.GetMethod(),
Value: in.GetValue(),
Response: in.GetResponse(),
Params: bytesToInterface(in.GetParams())}
Params: byteMapToInterfaceMap(in.GetParams())}
}

func pbClientCredsDeserialize(in []*pbx.ClientCred) []MsgCredClient {
Expand Down
2 changes: 1 addition & 1 deletion server/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
for i := range creds {
cr := &creds[i]
vld := store.GetValidator(cr.Method)
if err := vld.PreCheck(cr.Value, cr.Params); err != nil {
if _, err := vld.PreCheck(cr.Value, cr.Params); err != nil {
log.Println("create user: failed credential pre-check", cr, err, s.sid)
s.queueOut(decodeStoreError(err, msg.Id, "", msg.timestamp,
map[string]interface{}{"what": cr.Method}))
Expand Down
38 changes: 18 additions & 20 deletions server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"fmt"
"log"
"net"
"net/mail"
"path/filepath"
"reflect"
"regexp"
Expand All @@ -21,6 +20,7 @@ import (
"unicode/utf8"

"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/types"

"golang.org/x/crypto/acme/autocert"
Expand All @@ -35,10 +35,6 @@ var prefixedTagRegexp = regexp.MustCompile(`^([a-z]\w{1,15}):[-_+.!?#@\pL\pN]{1,
// Generic tag: the same restrictions as tag body.
var tagRegexp = regexp.MustCompile(`^[-_+.!?#@\pL\pN]{1,96}$`)

// Token suitable as a login: 3-16 chars, starts with a Unicode letter (class L) and contains Unicode letters (L),
// numbers (N) and underscore.
var basicLoginName = regexp.MustCompile(`^\pL[_\pL\pN]{2,15}$`)

const nullValue = "\u2421"

// Convert a list of IDs into ranges
Expand Down Expand Up @@ -425,24 +421,26 @@ func rewriteTag(orig, countryCode string, withLogin bool) string {
return orig
}

// Is it email?
if addr, err := mail.ParseAddress(orig); err == nil {
if len([]rune(addr.Address)) < maxTagLength && addr.Address == orig {
return "email:" + orig
// Check if token can be rewritten by any of the validators
param := map[string]interface{}{"countryCode": countryCode}
for name, conf := range globals.validators {
if conf.addToTags {
val := store.GetValidator(name)
if tag, _ := val.PreCheck(orig, param); tag != "" {
return tag
}
}
log.Println("failed to rewrite email tag", orig)
return ""
}

if num, err := phonenumbers.Parse(orig, countryCode); err == nil {
// It's a phone number. Not checking the length because phone numbers cannot be that long.
return "tel:" + phonenumbers.Format(num, phonenumbers.E164)
}

// Does it look like a username/login?
// TODO: use configured authenticators to check if orig is a valid user name.
if withLogin && basicLoginName.MatchString(orig) {
return "basic:" + orig
// Try authenticators now.
if withLogin {
auths := store.GetAuthNames()
for _, name := range auths {
auth := store.GetAuthHandler(name)
if tag := auth.AsTag(orig); tag != "" {
return tag
}
}
}

if tagRegexp.MatchString(orig) {
Expand Down

0 comments on commit 0897120

Please sign in to comment.