コード例 #1
0
ファイル: auth.go プロジェクト: z0rr0/luss
// DisableUsers deactivates users' accounts.
// Administrator permissions should be checked before this call.
func DisableUsers(ctx context.Context, names []string) ([]UserResult, error) {
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, err
	}
	coll, err := db.Coll(s, "users")
	if err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	result := make([]UserResult, len(names))
	for i, name := range names {
		err = coll.Update(bson.M{"_id": name, "off": false}, bson.M{"$set": bson.M{"off": true, "mt": now}})
		if err != nil {
			errMsg := "internal error"
			if err == mgo.ErrNotFound {
				errMsg = "not found"
			}
			result[i] = UserResult{Name: name, Err: errMsg}
			continue
		}
		result[i] = UserResult{Name: name, Err: ""}
	}
	return result, nil
}
コード例 #2
0
ファイル: trim.go プロジェクト: z0rr0/luss
// Lengthen converts a short link to original one.
// It uses own database session if it's needed
// or it gets data from the cache.
func Lengthen(ctx context.Context, short string) (*CustomURL, error) {
	c, err := conf.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	cache, cacheOn := c.Cache.Strorage["URL"]
	if cacheOn {
		if cu, ok := cache.Get(short); ok {
			// c.L.Debug.Println("read from LRU cache", short)
			return cu.(*CustomURL), nil
		}
	}
	num, err := Decode(short)
	if err != nil {
		return nil, err
	}
	s, err := db.NewSession(c.Conn, false)
	if err != nil {
		return nil, err
	}
	defer s.Close()
	coll, err := db.Coll(s, "urls")
	if err != nil {
		return nil, err
	}
	cu := &CustomURL{}
	err = coll.Find(bson.D{{Name: "_id", Value: num}, {Name: "off", Value: false}}).One(cu)
	if err != nil {
		return nil, err
	}
	if cacheOn {
		cache.Add(short, cu)
	}
	return cu, nil
}
コード例 #3
0
ファイル: trim.go プロジェクト: z0rr0/luss
// Shorten returns new short links.
func Shorten(ctx context.Context, params []*ReqParams) ([]*CustomURL, error) {
	c, err := conf.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	u, err := auth.ExtractUser(ctx)
	if err != nil {
		return nil, err
	}
	// check URLs pack size
	n := len(params)
	if n > c.Settings.MaxPack {
		return nil, fmt.Errorf("too big ReqParams pack size [%v]", n)
	}
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, err
	}
	// prepare
	coll, err := db.Coll(s, "urls")
	if err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	err = db.LockURL(s)
	if err != nil {
		return nil, err
	}
	defer db.UnlockURL(s)
	num, err := getMax(coll)
	if err != nil {
		return nil, err
	}
	documents := make([]interface{}, n)
	cus := make([]*CustomURL, n)
	for i, param := range params {
		num++
		cus[i] = &CustomURL{
			ID:        num,
			Group:     param.Group,
			Tag:       param.Tag,
			Original:  param.Original,
			User:      u.Name,
			TTL:       param.TTL,
			NotDirect: param.NotDirect,
			Created:   now,
			Modified:  now,
			Cb:        param.Cb,
			API:       param.IsAPI,
		}
		documents[i] = cus[i]
	}
	err = coll.Insert(documents...)
	if err != nil {
		return nil, err
	}
	return cus, nil
}
コード例 #4
0
ファイル: trim.go プロジェクト: z0rr0/luss
// Export exports URLs data.
func Export(ctx context.Context, filter Filter) ([]*CustomURL, [3]int, error) {
	var result []*CustomURL
	pages := [3]int{1, 1, filter.PageSize}
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, pages, err
	}
	coll, err := db.Coll(s, "urls")
	if err != nil {
		return nil, pages, err
	}
	conditions := bson.M{"group": filter.Group, "tag": filter.Tag}
	if filter.Active {
		conditions["off"] = false
	}
	switch {
	case filter.Period[0] != nil && filter.Period[1] != nil:
		conditions["$and"] = []bson.M{
			bson.M{"ts": bson.M{"$gte": *filter.Period[0]}},
			bson.M{"ts": bson.M{"$lte": *filter.Period[1]}},
		}
	case filter.Period[0] != nil:
		conditions["ts"] = bson.M{"$gte": *filter.Period[0]}
	case filter.Period[1] != nil:
		conditions["ts"] = bson.M{"$lte": *filter.Period[1]}
	}
	// TODO: add skip+limit
	n, err := coll.Find(conditions).Count()
	if err != nil {
		return nil, pages, err
	}
	if n == 0 {
		return result, pages, nil
	}
	pages[0] = filter.Page
	pages[1] = n / pages[2]
	if n%pages[2] != 0 {
		pages[1]++
	}
	switch {
	case pages[0] < 1:
		pages[0] = 1
	case pages[0] > pages[1]:
		pages[0] = pages[1]
	}
	err = coll.Find(conditions).Skip((pages[0] - 1) * pages[2]).Limit(pages[2]).Sort("-id").All(&result)
	if err != nil {
		return nil, pages, err
	}
	return result, pages, nil
}
コード例 #5
0
ファイル: auth.go プロジェクト: z0rr0/luss
// ChangeUsers updates user's tokens.
// Administrator permissions should be checked before this call.
func ChangeUsers(ctx context.Context, names []string) ([]UserResult, error) {
	c, err := conf.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	user, err := ExtractUser(ctx)
	if err != nil {
		return nil, err
	}
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, err
	}
	coll, err := db.Coll(s, "users")
	if err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	isAdmin := user.HasRole("admin")
	result := make([]UserResult, len(names))
	for i, name := range names {
		if !isAdmin && user.Name != name {
			result[i] = UserResult{Name: name, U: nil, Err: "permissions error"}
			continue
		}
		token, hash, err := genToken(c)
		if err != nil {
			result[i] = UserResult{Name: name, U: nil, Err: "internal error"}
			continue
		}
		err = coll.UpdateId(name, bson.M{"$set": bson.M{"token": hash, "mt": now}})
		if err != nil {
			errMsg := "internal error"
			if err == mgo.ErrNotFound {
				errMsg = "not found"
			}
			result[i] = UserResult{Name: name, U: nil, Err: errMsg}
			continue
		}
		result[i] = UserResult{Name: name, U: nil, T: token + hash, Err: ""}
	}
	return result, nil
}
コード例 #6
0
ファイル: auth.go プロジェクト: z0rr0/luss
// InitUsers initializes admin and anonymous users.
func InitUsers(c *conf.Config) error {
	s, err := db.NewSession(c.Conn, false)
	if err != nil {
		return err
	}
	defer s.Close()
	coll, err := db.Coll(s, "users")
	if err != nil {
		return err
	}
	b, err := hex.DecodeString(c.Listener.Security.Admin)
	if err != nil {
		return err
	}
	h := tokenHash(b, c)
	now := time.Now().UTC()
	users := []*User{
		{
			Name:     "admin",
			Disabled: false,
			Token:    hex.EncodeToString(h),
			Roles:    []string{"admin"},
			Modified: now,
			Created:  now,
		},
		{
			Name:     Anonymous,
			Disabled: false,
			Token:    "",
			Roles:    []string{},
			Modified: now,
			Created:  now,
		},
	}
	for _, u := range users {
		err := coll.Insert(u)
		if err != nil && !mgo.IsDup(err) {
			return err
		}
	}
	return nil
}
コード例 #7
0
ファイル: core.go プロジェクト: z0rr0/luss
// clean disables expired short URLs.
func clean(c *conf.Config) error {
	var change int
	s, err := db.NewSession(c.Conn, false)
	if err != nil {
		return err
	}
	defer s.Close()
	coll, err := db.Coll(s, "urls")
	if err != nil {
		return err
	}
	condition := bson.D{
		{Name: "off", Value: false},
		{Name: "ttl", Value: bson.M{"$lt": time.Now().UTC()}},
	}
	update := bson.M{"$set": bson.M{"off": true}}
	cache, cacheOn := c.Cache.Strorage["URL"]
	if cacheOn {
		cu := &trim.CustomURL{}
		iter := coll.Find(condition).Iter()
		for iter.Next(cu) {
			if err := coll.UpdateId(cu.ID, update); err == nil {
				cache.Remove(cu.String())
				change++
			}
		}
		err = iter.Close()
		if err != nil {
			return err
		}
	} else {
		// cache is disable, update only URLs
		info, err := coll.UpdateAll(condition, update)
		if err != nil {
			return err
		}
		change = info.Updated
	}
	c.L.Debug.Printf("cleaned %v item(s)", change)
	return nil
}
コード例 #8
0
ファイル: auth.go プロジェクト: z0rr0/luss
// CreateUsers creates new users.
func CreateUsers(ctx context.Context, names []string) ([]UserResult, error) {
	c, err := conf.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, err
	}
	coll, err := db.Coll(s, "users")
	if err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	result := make([]UserResult, len(names))
	for i, name := range names {
		token, hash, err := genToken(c)
		if err != nil {
			result[i] = UserResult{Name: name, U: nil, Err: "internal error"}
			continue
		}
		user := &User{
			Name:     name,
			Token:    hash,
			Roles:    []string{"user"},
			Modified: now,
			Created:  now,
		}
		if err := coll.Insert(user); err != nil {
			if mgo.IsDup(err) {
				result[i] = UserResult{Name: name, U: nil, Err: "duplicate item"}
			} else {
				result[i] = UserResult{Name: name, U: nil, Err: "internal error"}
			}
			continue
		}
		result[i] = UserResult{Name: name, U: user, T: token + hash, Err: ""}
	}
	return result, nil
}
コード例 #9
0
ファイル: stats.go プロジェクト: z0rr0/luss
// Tracker saves info about short URL activities.
// GeoIP database can be loaded from
// http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.mmdb.gz
func Tracker(ctx context.Context, cu *trim.CustomURL, addr string) error {
	c, err := conf.FromContext(ctx)
	if err != nil {
		return err
	}
	host, _, err := net.SplitHostPort(addr)
	if err != nil {
		return err
	}
	geo := GeoData{IP: host}
	record, err := c.GeoDB.City(net.ParseIP(host))
	if err != nil {
		// not critical: skip GeoIP data filling
		c.L.Error.Println(err)
	} else {
		geo.Country = record.Country.Names["en"]
		geo.City = record.City.Names["en"]
		geo.Latitude = record.Location.Latitude
		geo.Longitude = record.Location.Longitude
		geo.Tz = record.Location.TimeZone
	}
	s, err := db.NewSession(c.Conn, true)
	if err != nil {
		return err
	}
	defer s.Close()
	coll, err := db.Coll(s, "tracks")
	if err != nil {
		return err
	}
	err = coll.Insert(bson.M{
		"short": cu.String(),
		"url":   cu.Original,
		"group": cu.Group,
		"tag":   cu.Tag,
		"geo":   geo,
		"ts":    time.Now().UTC(),
	})
	return err
}
コード例 #10
0
ファイル: trim.go プロジェクト: z0rr0/luss
// MultiLengthen returns short URLs info for slice of links.
func MultiLengthen(ctx context.Context, links []string) ([]ChangeResult, error) {
	var result []ChangeResult
	c, err := conf.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	n := len(links)
	if n > c.Settings.MaxPack {
		return nil, fmt.Errorf("too big pack size [%v]", n)
	}
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, err
	}
	coll, err := db.Coll(s, "urls")
	if err != nil {
		return nil, err
	}
	for _, link := range links {
		id, err := Decode(link)
		if err != nil {
			c.L.Error.Printf("decode error [%v]: %v", link, err)
			result = append(result, ChangeResult{Cu: &CustomURL{ID: id}, Err: "invalid value"})
			continue
		}
		cu := &CustomURL{}
		err = coll.FindId(id).One(cu)
		if err != nil {
			msg := "internal error"
			if err == mgo.ErrNotFound {
				msg = "not found"
			}
			result = append(result, ChangeResult{Cu: &CustomURL{ID: id}, Err: msg})
			continue
		}
		result = append(result, ChangeResult{Cu: cu})
	}
	return result, nil
}
コード例 #11
0
ファイル: auth_test.go プロジェクト: z0rr0/luss
func TestCreateUser(t *testing.T) {
	const userName = "******"
	cfg, err := conf.Parse(test.TcConfigName())
	if err != nil {
		t.Fatalf("invalid behavior")
	}
	err = cfg.Validate()
	if err != nil {
		t.Fatalf("invalid behavior")
	}
	ctx, cancel := context.WithCancel(conf.NewContext(cfg))
	defer cancel()

	s, err := db.NewSession(cfg.Conn, true)
	if err != nil {
		t.Fatal(err)
	}
	defer s.Close()
	ctx = db.NewContext(ctx, s)

	coll, err := db.Coll(s, "users")
	if err != nil {
		t.Fatal(err)
	}
	_, err = coll.RemoveAll(nil)
	if err != nil {
		t.Fatal(err)
	}
	err = InitUsers(cfg)
	if err != nil {
		t.Fatal(err)
	}
	if n, err := coll.Count(); err != nil || n != 2 {
		t.Errorf("n=%v, err=%v", n, err)
	}

	users, err := CreateUsers(ctx, []string{userName})
	if err != nil {
		t.Fatal(err)
	}
	if users, err := CreateUsers(ctx, []string{userName}); err == nil {
		if users[0].Err == "" {
			t.Error("invalid behavior")
		}
	}
	r := &http.Request{PostForm: url.Values{"token": {users[0].T}}}
	ctx, err = CheckToken(ctx, r, false)
	if err != nil {
		t.Errorf("invalid behavior: %v", err)
	}
	ctx, err = Authenticate(ctx)
	if err != nil {
		t.Fatal(err)
	}
	u, err := ExtractUser(ctx)
	if err != nil {
		t.Fatal(err)
	}
	if u.String() != userName {
		t.Error("invalid behavior")
	}
	if !u.HasRole("user") {
		t.Error("invalid behavior")
	}
	if u.IsAnonymous() {
		t.Error("invalid behavior")
	}
	_, err = ChangeUsers(ctx, []string{userName})
	if err != nil {
		t.Fatal(err)
	}
	if result, err := ChangeUsers(ctx, []string{"bad"}); err != nil {
		if result[0].Err == "" {
			t.Error("invalid behavior")
		}
	}
	_, err = DisableUsers(ctx, []string{userName})
	if err != nil {
		t.Fatal(err)
	}
	if result, err := DisableUsers(ctx, []string{"bad"}); err != nil {
		if result[0].Err == "" {
			t.Error("invalid behavior")
		}
	}
}
コード例 #12
0
ファイル: trim.go プロジェクト: z0rr0/luss
// Import imports short URLs.
func Import(ctx context.Context, links map[string]*ReqParams) ([]ChangeResult, error) {
	var result []ChangeResult
	c, err := conf.FromContext(ctx)
	if err != nil {
		return nil, err
	}
	u, err := auth.ExtractUser(ctx)
	if err != nil {
		return nil, err
	}
	n := len(links)
	if n > c.Settings.MaxPack {
		return nil, fmt.Errorf("too big pack size [%v]", n)
	}
	s, err := db.CtxSession(ctx)
	if err != nil {
		return nil, err
	}
	// prepare
	coll, err := db.Coll(s, "urls")
	if err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	for short, param := range links {
		num, err := Decode(short)
		if err != nil {
			result = append(result, ChangeResult{Err: "invalid short URL value"})
			continue
		}
		cu := &CustomURL{
			ID:        num,
			Group:     param.Group,
			Tag:       param.Tag,
			Original:  param.Original,
			User:      u.Name,
			TTL:       param.TTL,
			NotDirect: param.NotDirect,
			Created:   now,
			Modified:  now,
			Cb:        param.Cb,
			API:       param.IsAPI,
		}
		// a locking of every insert is not fast
		// but the pack doesn't lock other operations.
		err = db.LockURL(s)
		if err != nil {
			return nil, err
		}
		errIns := coll.Insert(cu)
		err = db.UnlockURL(s)
		if err != nil {
			return nil, err
		}
		if errIns != nil {
			msg := "internal error"
			if mgo.IsDup(errIns) {
				msg = "duplicate item"
			}
			result = append(result, ChangeResult{Err: msg})
			continue
		}
		result = append(result, ChangeResult{Cu: cu})
	}
	return result, nil
}