Skip to content

Commit 0897120

Browse files
committed
converting hard-coded prefixes to calls to validators and authenticators
1 parent 21b6d71 commit 0897120

File tree

14 files changed

+457
-355
lines changed

14 files changed

+457
-355
lines changed

pbx/model.pb.go

+221-220
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pbx/model.proto

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ message ClientCred {
8282
string value = 2;
8383
// Verification response
8484
string response = 3;
85-
// Request parameters, such as preferences.
86-
bytes params = 4;
85+
// Request parameters, such as preferences or country code.
86+
map<string, bytes> params = 4;
8787
}
8888

8989
// SetDesc: C2S in set.what == "desc" and sub.init message

py_grpc/tinode_grpc/model_pb2.py

+146-98
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rest-auth/auth.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,8 @@ def upd():
106106

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

112112
@app.errorhandler(404)
113113
def not_found(error):

server/auth/anon/auth_anon.go

+5
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ func (authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
3939
return nil, nil, types.ErrUnsupported
4040
}
4141

42+
// AsTag is not supported, will produce an empty string.
43+
func (authenticator) AsTag(token string) string {
44+
return ""
45+
}
46+
4247
// IsUnique for a noop. Anonymous login does not use secret, any secret is fine.
4348
func (authenticator) IsUnique(secret []byte) (bool, error) {
4449
return true, nil

server/auth/auth.go

+4
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,10 @@ type AuthHandler interface {
223223
// Returns: user auth record, challenge, error.
224224
Authenticate(secret []byte) (*Rec, []byte, error)
225225

226+
// AsTag converts search token into prefixed tag or an empty string if it
227+
// cannot be represented as a prefixed tag.
228+
AsTag(token string) string
229+
226230
// IsUnique verifies if the provided secret can be considered unique by the auth scheme
227231
// E.g. if login is unique.
228232
IsUnique(secret []byte) (bool, error)

server/auth/basic/auth_basic.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -239,11 +239,17 @@ func (a *authenticator) Authenticate(secret []byte) (*auth.Rec, []byte, error) {
239239
State: types.StateUndefined}, nil, nil
240240
}
241241

242-
func (a *authenticator) PreCheck(secret []byte) (string, error) {
243-
if err := a.checkLoginPolicy(uname); err != nil {
244-
return "", err
242+
// AsTag convert search token into a prefixed tag, if possible.
243+
func (a *authenticator) AsTag(token string) string {
244+
if !a.addToTags {
245+
return ""
246+
}
247+
248+
if err := a.checkLoginPolicy(token); err != nil {
249+
return ""
245250
}
246-
return a.name + ":" + uname
251+
252+
return a.name + ":" + token
247253
}
248254

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

281287
// RestrictedTags returns tag namespaces (prefixes) restricted by this adapter.
282288
func (a *authenticator) RestrictedTags() ([]string, error) {
283-
var tags []string
289+
var prefix []string
284290
if a.addToTags {
285-
tags = []string{a.name}
291+
prefix = []string{a.name}
286292
}
287-
return tags, nil
293+
return prefix, nil
288294
}
289295

290296
// GetResetParams returns authenticator parameters passed to password reset handler.

server/auth/rest/README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,9 @@ If accounts are managed by the server, the server should respond with an error `
321321

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

324-
Server may enforce certain tag namespaces to be restricted, i.e. not editable by the user.
324+
Server may enforce certain tag namespaces (tag prefixes) to be restricted, i.e. not editable by the user.
325+
These are also used when searching for users.
326+
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==`.
325327

326328
#### Sample request
327329
```json
@@ -333,6 +335,7 @@ Server may enforce certain tag namespaces to be restricted, i.e. not editable by
333335
#### Sample response
334336
```json
335337
{
336-
"strarr": ["basic", "email", "tel"]
338+
"strarr": ["basic", "email", "tel"],
339+
"byteval": "XlthLXowLTlfXXszLDh9JA=="
337340
}
338341
```

server/auth/rest/auth_rest.go

+32
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"encoding/json"
77
"errors"
88
"io/ioutil"
9+
"log"
910
"net/http"
1011
"net/url"
12+
"regexp"
1113
"strings"
1214
"time"
1315

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

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

211+
// AsTag converts search token into prefixed tag or an empty string if it
212+
// cannot be represented as a prefixed tag.
213+
func (a *authenticator) AsTag(token string) string {
214+
if len(a.rTagNS) > 0 {
215+
if a.reToken != nil && !a.reToken.MatchString(token) {
216+
return ""
217+
}
218+
// No validation or passed validation.
219+
return a.rTagNS[0] + ":" + token
220+
}
221+
return ""
222+
}
223+
205224
// IsUnique verifies if the provided secret can be considered unique by the auth scheme
206225
// E.g. if login is unique.
207226
func (a *authenticator) IsUnique(secret []byte) (bool, error) {
@@ -231,11 +250,24 @@ func (a *authenticator) DelRecords(uid types.Uid) error {
231250

232251
// RestrictedTags returns tag namespaces (prefixes, such as prefix:login) restricted by the server.
233252
func (a *authenticator) RestrictedTags() ([]string, error) {
253+
if a.rTagNS != nil {
254+
ns := make([]string, len(a.rTagNS))
255+
copy(ns, a.rTagNS)
256+
return ns, nil
257+
}
234258
resp, err := a.callEndpoint("rtagns", nil, nil)
235259
if err != nil {
236260
return nil, err
237261
}
238262

263+
// Save valid result to cache.
264+
a.rTagNS = resp.StrSliceVal
265+
if len(resp.ByteVal) > 0 {
266+
a.reToken, err = regexp.Compile(string(resp.ByteVal))
267+
if err != nil {
268+
log.Println("rest_auth: invalid token regexp", string(resp.ByteVal))
269+
}
270+
}
239271
return resp.StrSliceVal, nil
240272
}
241273

server/auth/token/auth_token.go

+5
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ func (ta *authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, error) {
157157
return buf.Bytes(), expires, nil
158158
}
159159

160+
// AsTag is not supported, will produce an empty string.
161+
func (authenticator) AsTag(token string) string {
162+
return ""
163+
}
164+
160165
// IsUnique is not supported, will produce an error.
161166
func (authenticator) IsUnique(token []byte) (bool, error) {
162167
return false, types.ErrUnsupported

server/datamodel.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ type MsgCredClient struct {
7373
// Verification response
7474
Response string `json:"resp,omitempty"`
7575
// Request parameters, such as preferences. Passed to valiator without interpretation.
76-
Params interface{} `json:"params,omitempty"`
76+
Params map[string]interface{} `json:"params,omitempty"`
7777
}
7878

7979
// MsgSetQuery is an update to topic metadata: Desc, subscriptions, or tags.

server/pbconverter.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -921,7 +921,7 @@ func pbClientCredSerialize(in *MsgCredClient) *pbx.ClientCred {
921921
Method: in.Method,
922922
Value: in.Value,
923923
Response: in.Response,
924-
Params: interfaceToBytes(in.Params)}
924+
Params: interfaceMapToByteMap(in.Params)}
925925

926926
}
927927

@@ -947,7 +947,7 @@ func pbClientCredDeserialize(in *pbx.ClientCred) *MsgCredClient {
947947
Method: in.GetMethod(),
948948
Value: in.GetValue(),
949949
Response: in.GetResponse(),
950-
Params: bytesToInterface(in.GetParams())}
950+
Params: byteMapToInterfaceMap(in.GetParams())}
951951
}
952952

953953
func pbClientCredsDeserialize(in []*pbx.ClientCred) []MsgCredClient {

server/user.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
7676
for i := range creds {
7777
cr := &creds[i]
7878
vld := store.GetValidator(cr.Method)
79-
if err := vld.PreCheck(cr.Value, cr.Params); err != nil {
79+
if _, err := vld.PreCheck(cr.Value, cr.Params); err != nil {
8080
log.Println("create user: failed credential pre-check", cr, err, s.sid)
8181
s.queueOut(decodeStoreError(err, msg.Id, "", msg.timestamp,
8282
map[string]interface{}{"what": cr.Method}))

server/utils.go

+18-20
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"fmt"
1010
"log"
1111
"net"
12-
"net/mail"
1312
"path/filepath"
1413
"reflect"
1514
"regexp"
@@ -21,6 +20,7 @@ import (
2120
"unicode/utf8"
2221

2322
"github.com/tinode/chat/server/auth"
23+
"github.com/tinode/chat/server/store"
2424
"github.com/tinode/chat/server/store/types"
2525

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

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

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

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

437-
if num, err := phonenumbers.Parse(orig, countryCode); err == nil {
438-
// It's a phone number. Not checking the length because phone numbers cannot be that long.
439-
return "tel:" + phonenumbers.Format(num, phonenumbers.E164)
440-
}
441-
442-
// Does it look like a username/login?
443-
// TODO: use configured authenticators to check if orig is a valid user name.
444-
if withLogin && basicLoginName.MatchString(orig) {
445-
return "basic:" + orig
435+
// Try authenticators now.
436+
if withLogin {
437+
auths := store.GetAuthNames()
438+
for _, name := range auths {
439+
auth := store.GetAuthHandler(name)
440+
if tag := auth.AsTag(orig); tag != "" {
441+
return tag
442+
}
443+
}
446444
}
447445

448446
if tagRegexp.MatchString(orig) {

0 commit comments

Comments
 (0)