Dev (#487)
* add rdb config auth.debug for white_list * update prober config support mode param * feature: support access-token control with max connection, idle time, ... * add token/session delete with auth check * enable debug user for auth * skip init sso db if not enable
This commit is contained in:
parent
fb1354898c
commit
543d345aea
|
@ -1,3 +1,4 @@
|
|||
mode: whitelist # whitelist(default),all
|
||||
metrics:
|
||||
- name: mysql_queries
|
||||
type: COUNTER
|
||||
|
|
|
@ -1 +1,5 @@
|
|||
set names utf8;
|
||||
use n9e_rdb;
|
||||
|
||||
alter table session add `access_token` char(128) default '' after sid;
|
||||
alter table session add key (`access_token`);
|
||||
|
|
|
@ -344,12 +344,14 @@ CREATE TABLE `white_list` (
|
|||
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
|
||||
|
||||
CREATE TABLE `session` (
|
||||
`sid` char(128) NOT NULL,
|
||||
`username` varchar(64) DEFAULT '',
|
||||
`remote_addr` varchar(32) DEFAULT '',
|
||||
`created_at` integer unsigned DEFAULT '0',
|
||||
`updated_at` integer unsigned DEFAULT '0' NOT NULL,
|
||||
`sid` char(128) NOT NULL,
|
||||
`access_token` char(128) DEFAULT '',
|
||||
`username` varchar(64) DEFAULT '',
|
||||
`remote_addr` varchar(32) DEFAULT '',
|
||||
`created_at` integer unsigned DEFAULT '0',
|
||||
`updated_at` integer unsigned DEFAULT '0' NOT NULL,
|
||||
PRIMARY KEY (`sid`),
|
||||
KEY (`access_token`),
|
||||
KEY (`username`),
|
||||
KEY (`updated_at`)
|
||||
) ENGINE = InnoDB DEFAULT CHARACTER SET = utf8;
|
||||
|
|
|
@ -9,11 +9,12 @@ import (
|
|||
)
|
||||
|
||||
type Session struct {
|
||||
Sid string `json:"sid"`
|
||||
Username string `json:"username"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
Sid string `json:"sid"`
|
||||
AccessToken string `json:"-"`
|
||||
Username string `json:"username"`
|
||||
RemoteAddr string `json:"remote_addr"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
func SessionAll() (int64, error) {
|
||||
|
@ -37,12 +38,12 @@ func SessionGet(sid string) (*Session, error) {
|
|||
return &obj, nil
|
||||
}
|
||||
|
||||
func SessionInsert(in *Session) error {
|
||||
_, err := DB["rdb"].Insert(in)
|
||||
func (s *Session) Save() error {
|
||||
_, err := DB["rdb"].Insert(s)
|
||||
return err
|
||||
}
|
||||
|
||||
func SessionDel(sid string) error {
|
||||
func SessionDelete(sid string) error {
|
||||
_, err := DB["rdb"].Where("sid=?", sid).Delete(new(Session))
|
||||
return err
|
||||
}
|
||||
|
@ -52,11 +53,6 @@ func SessionUpdate(in *Session) error {
|
|||
return err
|
||||
}
|
||||
|
||||
func SessionCleanup(ts int64) error {
|
||||
n, err := DB["rdb"].Where("updated_at<?", ts).Delete(new(Session))
|
||||
logger.Debugf("delete before updated_at %d session %d", ts, n)
|
||||
return err
|
||||
}
|
||||
func SessionCleanupByCreatedAt(ts int64) error {
|
||||
n, err := DB["rdb"].Where("created_at<?", ts).Delete(new(Session))
|
||||
logger.Debugf("delete before created_at %d session %d", ts, n)
|
||||
|
@ -67,7 +63,20 @@ func (s *Session) Update(cols ...string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// SessionGetWithCache will update session.UpdatedAt
|
||||
func SessionGetByToken(token string) (*Session, error) {
|
||||
var obj Session
|
||||
has, err := DB["rdb"].Where("access_token=?", token).Get(&obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get session err %s", err)
|
||||
}
|
||||
if !has {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
// SessionGetWithCache will update session.UpdatedAt && token.LastAt
|
||||
func SessionGetWithCache(sid string) (*Session, error) {
|
||||
if sid == "" {
|
||||
return nil, fmt.Errorf("unable to get sid")
|
||||
|
@ -88,25 +97,8 @@ func SessionGetWithCache(sid string) (*Session, error) {
|
|||
sess.Update("updated_at")
|
||||
|
||||
if sess.Username != "" {
|
||||
cache.Set("sid."+sid, sess, time.Second*30)
|
||||
cache.Set("sid."+sid, sess, time.Second*10)
|
||||
}
|
||||
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
func SessionGetUserWithCache(sid string) (*User, error) {
|
||||
s, err := SessionGetWithCache(sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.Username == "" {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
|
||||
return UserMustGet("username=?", s.Username)
|
||||
}
|
||||
|
||||
func SessionCacheDelete(sid string) {
|
||||
cache.Delete("sid." + sid)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/toolkits/pkg/cache"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Id int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty" description:"access token name"`
|
||||
AccessToken string `json:"accessToken,omitempty"`
|
||||
RefreshToken string `json:"refreshToken,omitempty"`
|
||||
ClientId string `json:"clientId,omitempty"`
|
||||
Authorize string `json:"authorize,omitempty"`
|
||||
Previous string `json:"previous,omitempty"`
|
||||
ExpiresIn int64 `json:"expiresIn,omitempty" description:"max 3 year, default:0, max time"`
|
||||
Scope string `json:"scope,omitempty" description:"scope split by ' '"`
|
||||
RedirectUri string `json:"redirectUri,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
CreatedAt int64 `json:"createdAt,omitempty" out:",date"`
|
||||
LastAt int64 `json:"lastAt,omitempty" out:",date"`
|
||||
}
|
||||
|
||||
func TokenAll() (int64, error) {
|
||||
return DB["sso"].Count(new(Token))
|
||||
}
|
||||
|
||||
func TokenGet(token string) (*Token, error) {
|
||||
var obj Token
|
||||
has, err := DB["sso"].Where("access_token=?", token).Get(&obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get token err %s", err)
|
||||
}
|
||||
if !has {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return &obj, nil
|
||||
}
|
||||
|
||||
func (p *Token) Session() *Session {
|
||||
now := time.Now().Unix()
|
||||
return &Session{
|
||||
Sid: uuid.New().String(),
|
||||
AccessToken: p.AccessToken,
|
||||
Username: p.UserName,
|
||||
RemoteAddr: "",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Token) Update(cols ...string) error {
|
||||
_, err := DB["sso"].Where("access_token=?", p.AccessToken).Cols(cols...).Update(p)
|
||||
return err
|
||||
}
|
||||
|
||||
func TokenDelete(token string) error {
|
||||
_, err := DB["sso"].Where("access_token=?", token).Delete(new(Token))
|
||||
return err
|
||||
}
|
||||
|
||||
func TokenGets(where string, args ...interface{}) (tokens []Token, err error) {
|
||||
if where != "" {
|
||||
err = DB["sso"].Where(where, args...).Find(&tokens)
|
||||
} else {
|
||||
err = DB["sso"].Find(&tokens)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TokenGetWithCache will update token.LastAt
|
||||
func TokenGetWithCache(accessToken string) (*Session, error) {
|
||||
if accessToken == "" {
|
||||
return nil, fmt.Errorf("unable to get token")
|
||||
}
|
||||
|
||||
sess := &Session{}
|
||||
if err := cache.Get("access-token."+accessToken, &sess); err == nil {
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
if sess, err = SessionGetByToken(accessToken); err != nil {
|
||||
// try to get token from sso
|
||||
if t, err := TokenGet(accessToken); err != nil {
|
||||
return nil, fmt.Errorf("token not found")
|
||||
} else {
|
||||
sess = t.Session()
|
||||
sess.Save()
|
||||
}
|
||||
}
|
||||
|
||||
// update session
|
||||
sess.UpdatedAt = time.Now().Unix()
|
||||
sess.Update("updated_at")
|
||||
|
||||
cache.Set("access-token."+accessToken, sess, time.Second*10)
|
||||
|
||||
return sess, nil
|
||||
}
|
|
@ -4,6 +4,8 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
type WhiteList struct {
|
||||
|
@ -25,8 +27,9 @@ func WhiteListAccess(addr string) error {
|
|||
if ip == 0 {
|
||||
return fmt.Errorf("invalid remote address %s", addr)
|
||||
}
|
||||
logger.Debugf("WhiteListAccess htol(%s) %d", addr, ip)
|
||||
now := time.Now().Unix()
|
||||
count, _ := DB["rdb"].Where("start_ip_int<? and end_ip_int>? and start_time>? and end_time<?", ip, ip, now, now).Count(new(WhiteList))
|
||||
count, _ := DB["rdb"].Where("start_ip_int<=? and end_ip_int>=? and start_time<=? and end_time>=?", ip, ip, now, now).Count(new(WhiteList))
|
||||
if count == 0 {
|
||||
return fmt.Errorf("access deny from %s", addr)
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ type Field struct {
|
|||
Description string `json:"description,omitempty"`
|
||||
Required bool `json:"required,omitempty"`
|
||||
Items *Field `json:"items,omitempty" description:"arrays's items"`
|
||||
Type string `json:"type,omitempty" description:"struct,boolean,integer,folat,string,array"`
|
||||
Type string `json:"type,omitempty" description:"boolean,integer,folat,string,array"`
|
||||
Ref string `json:"$ref,omitempty" description:"name of the struct ref"`
|
||||
Fields []Field `json:"fields,omitempty" description:"fields of struct type"`
|
||||
Definitions map[string][]Field `json:"definitions,omitempty"`
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/didi/nightingale/src/modules/monapi/collector"
|
||||
"github.com/didi/nightingale/src/modules/prober/config"
|
||||
|
@ -22,23 +23,47 @@ type MetricConfig struct {
|
|||
}
|
||||
|
||||
type PluginConfig struct {
|
||||
Metrics []MetricConfig `metrics`
|
||||
Metrics []*MetricConfig `yaml:"metrics"`
|
||||
Mode string `yaml:"mode"`
|
||||
mode int `yaml:"-"`
|
||||
}
|
||||
|
||||
type CachePluginConfig struct {
|
||||
Name string
|
||||
Mode int
|
||||
Metrics map[string]*MetricConfig
|
||||
}
|
||||
|
||||
const (
|
||||
PluginModeWhitelist = iota
|
||||
PluginModeOverlay
|
||||
)
|
||||
|
||||
func (p *PluginConfig) Validate() error {
|
||||
switch strings.ToLower(p.Mode) {
|
||||
case "whitelist":
|
||||
p.mode = PluginModeWhitelist
|
||||
case "overlay":
|
||||
p.mode = PluginModeOverlay
|
||||
default:
|
||||
p.mode = PluginModeWhitelist
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
metricsConfig map[string]MetricConfig
|
||||
metricsExpr map[string]map[string]MetricConfig
|
||||
ignoreConfig bool
|
||||
metricsConfig map[string]*MetricConfig
|
||||
metricsExpr map[string]*CachePluginConfig
|
||||
)
|
||||
|
||||
func InitPluginsConfig(cf *config.ConfYaml) {
|
||||
metricsConfig = make(map[string]MetricConfig)
|
||||
metricsExpr = make(map[string]map[string]MetricConfig)
|
||||
ignoreConfig = cf.IgnoreConfig
|
||||
metricsConfig = make(map[string]*MetricConfig)
|
||||
metricsExpr = make(map[string]*CachePluginConfig)
|
||||
plugins := collector.GetRemoteCollectors()
|
||||
for _, plugin := range plugins {
|
||||
metricsExpr[plugin] = make(map[string]MetricConfig)
|
||||
pluginConfig := PluginConfig{}
|
||||
cacheConfig := newCachePluginConfig()
|
||||
config := PluginConfig{}
|
||||
metricsExpr[plugin] = cacheConfig
|
||||
|
||||
file := filepath.Join(cf.PluginsConfig, plugin+".yml")
|
||||
b, err := ioutil.ReadFile(file)
|
||||
|
@ -47,12 +72,19 @@ func InitPluginsConfig(cf *config.ConfYaml) {
|
|||
continue
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(b, &pluginConfig); err != nil {
|
||||
if err := yaml.Unmarshal(b, &config); err != nil {
|
||||
logger.Warningf("yaml.Unmarshal %s err %s", plugin, err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, v := range pluginConfig.Metrics {
|
||||
if err := config.Validate(); err != nil {
|
||||
logger.Warningf("%s Validate() err %s", plugin, err)
|
||||
continue
|
||||
}
|
||||
cacheConfig.Name = plugin
|
||||
cacheConfig.Mode = config.mode
|
||||
|
||||
for _, v := range config.Metrics {
|
||||
if _, ok := metricsConfig[v.Name]; ok {
|
||||
panic(fmt.Sprintf("plugin %s metrics %s is already exists", plugin, v.Name))
|
||||
}
|
||||
|
@ -65,8 +97,7 @@ func InitPluginsConfig(cf *config.ConfYaml) {
|
|||
panic(fmt.Sprintf("plugin %s metrics %s expr %s parse err %s",
|
||||
plugin, v.Name, v.Expr, err))
|
||||
}
|
||||
metricsExpr[plugin][v.Name] = v
|
||||
|
||||
cacheConfig.Metrics[v.Name] = v
|
||||
}
|
||||
}
|
||||
logger.Infof("loaded plugin config %s", file)
|
||||
|
@ -82,9 +113,9 @@ func (p *MetricConfig) Calc(vars map[string]float64) (float64, error) {
|
|||
return p.notations.Calc(vars)
|
||||
}
|
||||
|
||||
func Metric(metric string, typ telegraf.ValueType) (c MetricConfig, ok bool) {
|
||||
func Metric(metric string, typ telegraf.ValueType) (c *MetricConfig, ok bool) {
|
||||
c, ok = metricsConfig[metric]
|
||||
if !ok && !ignoreConfig {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -95,7 +126,7 @@ func Metric(metric string, typ telegraf.ValueType) (c MetricConfig, ok bool) {
|
|||
return
|
||||
}
|
||||
|
||||
func GetMetricExprs(pluginName string) (c map[string]MetricConfig, ok bool) {
|
||||
func GetMetricExprs(pluginName string) (c *CachePluginConfig, ok bool) {
|
||||
c, ok = metricsExpr[pluginName]
|
||||
return
|
||||
}
|
||||
|
@ -116,3 +147,9 @@ func metricType(typ telegraf.ValueType) string {
|
|||
return "GAUGE"
|
||||
}
|
||||
}
|
||||
|
||||
func newCachePluginConfig() *CachePluginConfig {
|
||||
return &CachePluginConfig{
|
||||
Metrics: map[string]*MetricConfig{},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ type ConfYaml struct {
|
|||
Report report.ReportSection `yaml:"report"`
|
||||
WorkerProcesses int `yaml:"workerProcesses"`
|
||||
PluginsConfig string `yaml:"pluginsConfig"`
|
||||
IgnoreConfig bool `yaml:"ignoreConfig"`
|
||||
HTTP HTTPSection `yaml:"http"`
|
||||
}
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@ const (
|
|||
)
|
||||
|
||||
type TokenNotation struct {
|
||||
tokenType tokenType
|
||||
o token.Token // operator
|
||||
v string // variable
|
||||
c float64 // const
|
||||
tokenType tokenType
|
||||
tokenOperator token.Token
|
||||
tokenVariable string
|
||||
tokenConst float64
|
||||
}
|
||||
|
||||
type Notations []*TokenNotation
|
||||
|
@ -38,11 +38,11 @@ func (s Notations) String() string {
|
|||
tn := s[i]
|
||||
switch tn.tokenType {
|
||||
case tokenOperator:
|
||||
out.WriteString(tn.o.String() + " ")
|
||||
out.WriteString(tn.tokenOperator.String() + " ")
|
||||
case tokenVar:
|
||||
out.WriteString(tn.v + " ")
|
||||
out.WriteString(tn.tokenVariable + " ")
|
||||
case tokenConst:
|
||||
out.WriteString(fmt.Sprintf("%.0f ", tn.c))
|
||||
out.WriteString(fmt.Sprintf("%.0f ", tn.tokenConst))
|
||||
}
|
||||
}
|
||||
return out.String()
|
||||
|
@ -67,18 +67,18 @@ func (rpn Notations) Calc(vars map[string]float64) (float64, error) {
|
|||
tn := rpn[i]
|
||||
switch tn.tokenType {
|
||||
case tokenVar:
|
||||
if v, ok := vars[tn.v]; !ok {
|
||||
return 0, fmt.Errorf("variable %s is not set", tn.v)
|
||||
if v, ok := vars[tn.tokenVariable]; !ok {
|
||||
return 0, fmt.Errorf("variable %s is not set", tn.tokenVariable)
|
||||
} else {
|
||||
logger.Debugf("get %s %f", tn.v, v)
|
||||
logger.Debugf("get %s %f", tn.tokenVariable, v)
|
||||
s.Push(v)
|
||||
}
|
||||
case tokenConst:
|
||||
s.Push(tn.c)
|
||||
s.Push(tn.tokenConst)
|
||||
case tokenOperator:
|
||||
op2 := s.Pop()
|
||||
op1 := s.Pop()
|
||||
switch tn.o {
|
||||
switch tn.tokenOperator {
|
||||
case token.ADD:
|
||||
s.Push(op1 + op2)
|
||||
case token.SUB:
|
||||
|
@ -123,9 +123,9 @@ func NewNotations(src []byte) (output Notations, err error) {
|
|||
return nil, fmt.Errorf("parseFloat error %s\t%s\t%q",
|
||||
fset.Position(pos), tok, lit)
|
||||
}
|
||||
output.Push(&TokenNotation{tokenType: tokenConst, c: c})
|
||||
output.Push(&TokenNotation{tokenType: tokenConst, tokenConst: c})
|
||||
case token.IDENT:
|
||||
output.Push(&TokenNotation{tokenType: tokenVar, v: lit})
|
||||
output.Push(&TokenNotation{tokenType: tokenVar, tokenVariable: lit})
|
||||
case token.LPAREN: // (
|
||||
s.Push(tok)
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO: // + - * /
|
||||
|
@ -135,7 +135,7 @@ func NewNotations(src []byte) (output Notations, err error) {
|
|||
} else if op := s.Top(); op == token.LPAREN || priority(tok) > priority(op) {
|
||||
s.Push(tok)
|
||||
} else {
|
||||
output.Push(&TokenNotation{tokenType: tokenOperator, o: s.Pop()})
|
||||
output.Push(&TokenNotation{tokenType: tokenOperator, tokenOperator: s.Pop()})
|
||||
goto opRetry
|
||||
}
|
||||
case token.RPAREN: // )
|
||||
|
@ -143,7 +143,7 @@ func NewNotations(src []byte) (output Notations, err error) {
|
|||
if op := s.Pop(); op == token.LPAREN {
|
||||
break
|
||||
} else {
|
||||
output.Push(&TokenNotation{tokenType: tokenOperator, o: op})
|
||||
output.Push(&TokenNotation{tokenType: tokenOperator, tokenOperator: op})
|
||||
}
|
||||
}
|
||||
default:
|
||||
|
@ -153,7 +153,7 @@ func NewNotations(src []byte) (output Notations, err error) {
|
|||
|
||||
out:
|
||||
for i, l := 0, s.Len(); i < l; i++ {
|
||||
output.Push(&TokenNotation{tokenType: tokenOperator, o: s.Pop()})
|
||||
output.Push(&TokenNotation{tokenType: tokenOperator, tokenOperator: s.Pop()})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -67,8 +67,7 @@ func (p *ruleEntity) calc() error {
|
|||
vars[v.Metric] = v.Value
|
||||
}
|
||||
|
||||
// TODO: add some variable from system or rule
|
||||
for _, config := range configs {
|
||||
for _, config := range configs.Metrics {
|
||||
f, err := config.Calc(vars)
|
||||
if err != nil {
|
||||
logger.Debugf("calc err %s", err)
|
||||
|
@ -85,6 +84,24 @@ func (p *ruleEntity) calc() error {
|
|||
ValueUntyped: f,
|
||||
})
|
||||
}
|
||||
|
||||
if configs.Mode == cache.PluginModeOverlay {
|
||||
for k, v := range vars {
|
||||
if _, ok := configs.Metrics[k]; ok {
|
||||
continue
|
||||
}
|
||||
p.metrics = append(p.metrics, &dataobj.MetricValue{
|
||||
Nid: sample.Nid,
|
||||
Metric: k,
|
||||
Timestamp: sample.Timestamp,
|
||||
Step: sample.Step,
|
||||
CounterType: "GAUGE",
|
||||
TagsMap: sample.TagsMap,
|
||||
Value: v,
|
||||
ValueUntyped: v,
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package auth
|
|||
import (
|
||||
"github.com/didi/nightingale/src/models"
|
||||
"github.com/didi/nightingale/src/modules/rdb/config"
|
||||
"github.com/didi/nightingale/src/modules/rdb/ssoc"
|
||||
)
|
||||
|
||||
var defaultAuth Authenticator
|
||||
|
@ -11,8 +12,8 @@ func Init(cf config.AuthExtraSection) {
|
|||
defaultAuth = *New(cf)
|
||||
}
|
||||
|
||||
func WhiteListAccess(remoteAddr string) error {
|
||||
return defaultAuth.WhiteListAccess(remoteAddr)
|
||||
func WhiteListAccess(user *models.User, remoteAddr string) error {
|
||||
return defaultAuth.WhiteListAccess(user, remoteAddr)
|
||||
}
|
||||
|
||||
// PostLogin check user status after login
|
||||
|
@ -28,10 +29,16 @@ func CheckPassword(password string) error {
|
|||
return defaultAuth.CheckPassword(password)
|
||||
}
|
||||
|
||||
// ChangePasswordRedirect check user should change password before login
|
||||
// return change password redirect url
|
||||
func ChangePasswordRedirect(user *models.User, redirect string) string {
|
||||
return defaultAuth.ChangePasswordRedirect(user, redirect)
|
||||
func PostCallback(in *ssoc.CallbackOutput) error {
|
||||
return defaultAuth.PostCallback(in)
|
||||
}
|
||||
|
||||
func DeleteSession(sid string) error {
|
||||
return defaultAuth.DeleteSession(sid)
|
||||
}
|
||||
|
||||
func DeleteToken(accessToken string) error {
|
||||
return defaultAuth.DeleteToken(accessToken)
|
||||
}
|
||||
|
||||
func Start() error {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -9,20 +10,28 @@ import (
|
|||
"github.com/didi/nightingale/src/models"
|
||||
"github.com/didi/nightingale/src/modules/rdb/cache"
|
||||
"github.com/didi/nightingale/src/modules/rdb/config"
|
||||
"github.com/didi/nightingale/src/modules/rdb/ssoc"
|
||||
"github.com/didi/nightingale/src/toolkits/i18n"
|
||||
pkgcache "github.com/toolkits/pkg/cache"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
ChangePasswordURL = "/change-password"
|
||||
loginModeFifo = true
|
||||
)
|
||||
|
||||
type Authenticator struct {
|
||||
extraMode bool
|
||||
whiteList bool
|
||||
debug bool
|
||||
debugUser string
|
||||
frozenTime int64
|
||||
writenOffTime int64
|
||||
userExpire bool
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// description:"enable user expire control, active -> frozen -> writen-off"
|
||||
|
@ -34,43 +43,22 @@ func New(cf config.AuthExtraSection) *Authenticator {
|
|||
return &Authenticator{
|
||||
extraMode: true,
|
||||
whiteList: cf.WhiteList,
|
||||
debug: cf.Debug,
|
||||
debugUser: cf.DebugUser,
|
||||
frozenTime: 86400 * int64(cf.FrozenDays),
|
||||
writenOffTime: 86400 * int64(cf.WritenOffDays),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Authenticator) WhiteListAccess(remoteAddr string) error {
|
||||
if !p.whiteList {
|
||||
func (p *Authenticator) WhiteListAccess(user *models.User, remoteAddr string) error {
|
||||
if !p.extraMode || !p.whiteList || (p.debug && user.Username != p.debugUser) {
|
||||
return nil
|
||||
}
|
||||
return models.WhiteListAccess(remoteAddr)
|
||||
}
|
||||
|
||||
// ChangePasswordRedirect check user should change password before login
|
||||
// return change password redirect url
|
||||
func (p *Authenticator) ChangePasswordRedirect(user *models.User, redirect string) string {
|
||||
if !p.extraMode {
|
||||
return ""
|
||||
if err := models.WhiteListAccess(remoteAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cf := cache.AuthConfig()
|
||||
|
||||
var reason string
|
||||
if user.PwdUpdatedAt == 0 {
|
||||
reason = _s("First Login, please change the password in time")
|
||||
} else if user.PwdUpdatedAt+cf.PwdExpiresIn*86400*30 < time.Now().Unix() {
|
||||
reason = _s("Password expired, please change the password in time")
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
|
||||
v := url.Values{
|
||||
"redirect": {redirect},
|
||||
"username": {user.Username},
|
||||
"reason": {reason},
|
||||
"pwdRules": cf.PwdRules(),
|
||||
}
|
||||
return ChangePasswordURL + "?" + v.Encode()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Authenticator) PostLogin(user *models.User, loginErr error) (err error) {
|
||||
|
@ -85,7 +73,7 @@ func (p *Authenticator) PostLogin(user *models.User, loginErr error) (err error)
|
|||
user.Update("status", "login_err_num", "locked_at", "updated_at", "logged_at")
|
||||
}()
|
||||
|
||||
if !p.extraMode || user == nil {
|
||||
if !p.extraMode || user == nil || (p.debug && user.Username != p.debugUser) {
|
||||
err = loginErr
|
||||
return
|
||||
}
|
||||
|
@ -192,42 +180,200 @@ func (p *Authenticator) CheckPassword(password string) error {
|
|||
return checkPassword(cache.AuthConfig(), password)
|
||||
}
|
||||
|
||||
// PostCallback between sso.Callback() and sessionLogin()
|
||||
func (p *Authenticator) PostCallback(in *ssoc.CallbackOutput) error {
|
||||
if !p.extraMode || (p.debug && in.User.Username != p.debugUser) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cf := cache.AuthConfig()
|
||||
|
||||
if err := p.changePasswordRedirect(in, cf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check user session limit
|
||||
tokens := []models.Token{}
|
||||
if maxCnt := int(cf.MaxSessionNumber); maxCnt > 0 {
|
||||
models.DB["sso"].SQL("select * from token where user_name=? order by id desc", in.User.Username).Find(&tokens)
|
||||
|
||||
if n := len(tokens); n > maxCnt {
|
||||
for i := maxCnt; i < n; i++ {
|
||||
logger.Debugf("[over limit] delete session by token %s %s", tokens[i].UserName, tokens[i].AccessToken)
|
||||
deleteSessionByToken(&tokens[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangePasswordRedirect check user should change password before login
|
||||
// return err when need changePassword
|
||||
func (p *Authenticator) changePasswordRedirect(in *ssoc.CallbackOutput, cf *models.AuthConfig) (err error) {
|
||||
if in.User.PwdUpdatedAt == 0 {
|
||||
err = _e("First Login, please change the password in time")
|
||||
} else if cf.PwdExpiresIn > 0 && in.User.PwdUpdatedAt+cf.PwdExpiresIn*86400*30 < time.Now().Unix() {
|
||||
err = _e("Password expired, please change the password in time")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
v := url.Values{
|
||||
"redirect": {in.Redirect},
|
||||
"username": {in.User.Username},
|
||||
"reason": {err.Error()},
|
||||
"pwdRules": cf.PwdRules(),
|
||||
}
|
||||
in.Redirect = ChangePasswordURL + "?" + v.Encode()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *Authenticator) DeleteSession(sid string) error {
|
||||
s, err := models.SessionGet(sid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !p.extraMode {
|
||||
pkgcache.Delete("sid." + s.Sid)
|
||||
models.SessionDelete(s.Sid)
|
||||
return nil
|
||||
}
|
||||
return deleteSession(s)
|
||||
}
|
||||
|
||||
func (p *Authenticator) DeleteToken(accessToken string) error {
|
||||
if !p.extraMode {
|
||||
return nil
|
||||
}
|
||||
token, err := models.TokenGet(accessToken)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return deleteSessionByToken(token)
|
||||
}
|
||||
|
||||
func (p *Authenticator) Stop() error {
|
||||
p.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Authenticator) Start() error {
|
||||
p.ctx, p.cancel = context.WithCancel(context.Background())
|
||||
|
||||
if !p.extraMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
t := time.NewTicker(5 * time.Second)
|
||||
defer t.Stop()
|
||||
for {
|
||||
now := time.Now().Unix()
|
||||
if p.frozenTime > 0 {
|
||||
// 3个月以上未登录,用户自动变为休眠状态
|
||||
if _, err := models.DB["rdb"].Exec("update user set status=?, updated_at=?, locked_at=? where ((logged_at > 0 and logged_at<?) or (logged_at == 0 and created_at < ?)) and status in (?,?,?)",
|
||||
models.USER_S_FROZEN, now, now, now-p.frozenTime,
|
||||
models.USER_S_ACTIVE, models.USER_S_INACTIVE, models.USER_S_LOCKED); err != nil {
|
||||
logger.Errorf("update user status error %s", err)
|
||||
}
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
p.cleanupSession()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if p.writenOffTime > 0 {
|
||||
// 变为休眠状态后1年未激活,用户自动变为已注销状态
|
||||
if _, err := models.DB["rdb"].Exec("update user set status=?, updated_at=? where locked_at<? and status=?",
|
||||
models.USER_S_WRITEN_OFF, now, now-p.writenOffTime, models.USER_S_FROZEN); err != nil {
|
||||
logger.Errorf("update user status error %s", err)
|
||||
}
|
||||
go func() {
|
||||
t := time.NewTicker(time.Hour)
|
||||
defer t.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-p.ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
p.updateUserStatus()
|
||||
}
|
||||
|
||||
// reset login err num before 24 hours ago
|
||||
if _, err := models.DB["rdb"].Exec("update user set login_err_num=0, updated_at=? where updated_at<? and login_err_num>0", now, now-86400); err != nil {
|
||||
logger.Errorf("update user login err num error %s", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Hour)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanup rdb.session & sso.token
|
||||
func (p *Authenticator) cleanupSession() {
|
||||
now := time.Now().Unix()
|
||||
cf := cache.AuthConfig()
|
||||
|
||||
// idle session cleanup
|
||||
if cf.MaxConnIdelTime > 0 {
|
||||
expiresAt := now - cf.MaxConnIdelTime*60
|
||||
sessions := []models.Session{}
|
||||
if err := models.DB["rdb"].SQL("select * from session where updated_at < ? and username <> '' ", expiresAt).Find(&sessions); err != nil {
|
||||
logger.Errorf("token idel time cleanup err %s", err)
|
||||
}
|
||||
|
||||
logger.Debugf("find %d idle sessions that should be clean up", len(sessions))
|
||||
|
||||
for _, s := range sessions {
|
||||
if p.debug && s.Username != p.debugUser {
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Debugf("[idle] deleteSession %s %s", s.Username, s.Sid)
|
||||
deleteSession(&s)
|
||||
}
|
||||
}
|
||||
|
||||
// session count limit cleanup
|
||||
if maxCnt := int(cf.MaxSessionNumber); maxCnt > 0 {
|
||||
tokens := []models.Token{}
|
||||
userName := ""
|
||||
cnt := 0
|
||||
|
||||
if err := models.DB["sso"].SQL("select * from token order by user_name, id desc").Find(&tokens); err != nil {
|
||||
logger.Errorf("token idel time cleanup err %s", err)
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
if userName != token.UserName {
|
||||
userName = token.UserName
|
||||
cnt = 0
|
||||
}
|
||||
|
||||
cnt++
|
||||
if cnt > maxCnt {
|
||||
if p.debug && token.UserName != p.debugUser {
|
||||
continue
|
||||
}
|
||||
logger.Debugf("[over limit] deleteSessionByToken %s %s idx %d max %d", token.UserName, token.AccessToken, cnt, maxCnt)
|
||||
deleteSessionByToken(&token)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Authenticator) updateUserStatus() {
|
||||
now := time.Now().Unix()
|
||||
if p.frozenTime > 0 {
|
||||
// 3个月以上未登录,用户自动变为休眠状态
|
||||
if _, err := models.DB["rdb"].Exec("update user set status=?, updated_at=?, locked_at=? where ((logged_at > 0 and logged_at<?) or (logged_at == 0 and created_at < ?)) and status in (?,?,?)",
|
||||
models.USER_S_FROZEN, now, now, now-p.frozenTime,
|
||||
models.USER_S_ACTIVE, models.USER_S_INACTIVE, models.USER_S_LOCKED); err != nil {
|
||||
logger.Errorf("update user status error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if p.writenOffTime > 0 {
|
||||
// 变为休眠状态后1年未激活,用户自动变为已注销状态
|
||||
if _, err := models.DB["rdb"].Exec("update user set status=?, updated_at=? where locked_at<? and status=?",
|
||||
models.USER_S_WRITEN_OFF, now, now-p.writenOffTime, models.USER_S_FROZEN); err != nil {
|
||||
logger.Errorf("update user status error %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// reset login err num before 24 hours ago
|
||||
if _, err := models.DB["rdb"].Exec("update user set login_err_num=0, updated_at=? where updated_at<? and login_err_num>0", now, now-86400); err != nil {
|
||||
logger.Errorf("update user login err num error %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func activeUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
|
||||
now := time.Now().Unix()
|
||||
|
||||
|
@ -250,7 +396,7 @@ func activeUserAccess(cf *models.AuthConfig, user *models.User, loginErr error)
|
|||
user.LoginErrNum = 0
|
||||
user.UpdatedAt = now
|
||||
|
||||
if cf.MaxSessionNumber > 0 {
|
||||
if cf.MaxSessionNumber > 0 && !loginModeFifo {
|
||||
if n, err := models.SessionUserAll(user.Username); err != nil {
|
||||
return err
|
||||
} else if n >= cf.MaxSessionNumber {
|
||||
|
@ -258,18 +404,6 @@ func activeUserAccess(cf *models.AuthConfig, user *models.User, loginErr error)
|
|||
}
|
||||
}
|
||||
|
||||
if cf.PwdExpiresIn > 0 && user.PwdUpdatedAt > 0 {
|
||||
// debug account
|
||||
// TODO: remove me
|
||||
if user.Username == "Demo.2022" {
|
||||
if now-user.PwdUpdatedAt > cf.PwdExpiresIn*60 {
|
||||
return _e("Password has been expired")
|
||||
}
|
||||
}
|
||||
if now-user.PwdUpdatedAt > cf.PwdExpiresIn*30*86400 {
|
||||
return _e("Password has been expired")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func inactiveUserAccess(cf *models.AuthConfig, user *models.User, loginErr error) error {
|
||||
|
@ -354,6 +488,25 @@ func checkPassword(cf *models.AuthConfig, passwd string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func deleteSession(s *models.Session) error {
|
||||
pkgcache.Delete("sid." + s.Sid)
|
||||
models.SessionDelete(s.Sid)
|
||||
pkgcache.Delete("access-token." + s.AccessToken)
|
||||
models.TokenDelete(s.AccessToken)
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteSessionByToken(t *models.Token) error {
|
||||
if s, _ := models.SessionGetByToken(t.AccessToken); s != nil {
|
||||
deleteSession(s)
|
||||
} else {
|
||||
pkgcache.Delete("access-token." + t.AccessToken)
|
||||
models.TokenDelete(t.AccessToken)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func _e(format string, a ...interface{}) error {
|
||||
return fmt.Errorf(i18n.Sprintf(format, a...))
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ func (p *configCache) AuthConfig() *models.AuthConfig {
|
|||
}
|
||||
|
||||
func (p *configCache) loop(ctx context.Context, interval int) {
|
||||
if err := p.update(); err != nil {
|
||||
logger.Errorf("configCache update err %s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
t := time.NewTicker(time.Duration(interval) * time.Second)
|
||||
defer t.Stop()
|
||||
|
|
|
@ -29,10 +29,12 @@ type authSection struct {
|
|||
}
|
||||
|
||||
type AuthExtraSection struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
WhiteList bool `yaml:"whiteList"`
|
||||
FrozenDays int `yaml:"frozenDays"`
|
||||
WritenOffDays int `yaml:"writenOffDays"`
|
||||
Enable bool `yaml:"enable"`
|
||||
Debug bool `yaml:"debug" description:"debug"`
|
||||
DebugUser string `yaml:"debugUser" description:"debug username"`
|
||||
WhiteList bool `yaml:"whiteList"`
|
||||
FrozenDays int `yaml:"frozenDays"`
|
||||
WritenOffDays int `yaml:"writenOffDays"`
|
||||
}
|
||||
|
||||
type wechatSection struct {
|
||||
|
|
|
@ -27,7 +27,6 @@ func shouldBeLogin() gin.HandlerFunc {
|
|||
return func(c *gin.Context) {
|
||||
sessionStart(c)
|
||||
username := mustUsername(c)
|
||||
logger.Debugf("set username %s", username)
|
||||
c.Set("username", username)
|
||||
c.Next()
|
||||
sessionUpdate(c)
|
||||
|
@ -137,14 +136,6 @@ func sessionUpdate(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
func sessionDestory(c *gin.Context) (sid string, err error) {
|
||||
if sid, err = session.Destroy(c.Writer, c.Request); sid != "" {
|
||||
models.SessionCacheDelete(sid)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func sessionUsername(c *gin.Context) string {
|
||||
s, ok := session.FromContext(c.Request.Context())
|
||||
if !ok {
|
||||
|
@ -153,7 +144,7 @@ func sessionUsername(c *gin.Context) string {
|
|||
return s.Get("username")
|
||||
}
|
||||
|
||||
func sessionLogin(c *gin.Context, username, remoteAddr string) {
|
||||
func sessionLogin(c *gin.Context, username, remoteAddr, accessToken string) {
|
||||
s, ok := session.FromContext(c.Request.Context())
|
||||
if !ok {
|
||||
logger.Warningf("session.Start() err not found sessionStore")
|
||||
|
@ -167,4 +158,8 @@ func sessionLogin(c *gin.Context, username, remoteAddr string) {
|
|||
logger.Warningf("session.Set() err %s", err)
|
||||
return
|
||||
}
|
||||
if err := s.Set("accessToken", accessToken); err != nil {
|
||||
logger.Warningf("session.Set() err %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,6 +203,11 @@ func Config(r *gin.Engine) {
|
|||
v1.GET("/sessions/:sid/user", v1SessionGetUser)
|
||||
v1.DELETE("/sessions/:sid", v1SessionDelete)
|
||||
|
||||
// token
|
||||
v1.GET("/tokens/:token", v1TokenGet)
|
||||
v1.GET("/tokens/:token/user", v1TokenGetUser)
|
||||
v1.DELETE("/tokens/:token", v1TokenDelete)
|
||||
|
||||
// 第三方系统同步权限表的数据
|
||||
v1.GET("/table/sync/role-operation", v1RoleOperationGets)
|
||||
v1.GET("/table/sync/role-global-user", v1RoleGlobalUserGets)
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/didi/nightingale/src/modules/rdb/cache"
|
||||
"github.com/didi/nightingale/src/modules/rdb/config"
|
||||
"github.com/didi/nightingale/src/modules/rdb/redisc"
|
||||
"github.com/didi/nightingale/src/modules/rdb/session"
|
||||
"github.com/didi/nightingale/src/modules/rdb/ssoc"
|
||||
)
|
||||
|
||||
|
@ -31,9 +32,6 @@ var (
|
|||
loginCodeEmailTpl *template.Template
|
||||
errUnsupportCaptcha = errors.New("unsupported captcha")
|
||||
|
||||
// TODO: set false
|
||||
debug = true
|
||||
|
||||
// https://captcha.mojotv.cn
|
||||
captchaDirver = base64Captcha.DriverString{
|
||||
Height: 30,
|
||||
|
@ -105,7 +103,7 @@ func login(c *gin.Context) {
|
|||
return err
|
||||
}
|
||||
|
||||
sessionLogin(c, user.Username, in.RemoteAddr)
|
||||
sessionLogin(c, user.Username, in.RemoteAddr, "")
|
||||
return nil
|
||||
}()
|
||||
renderMessage(c, err)
|
||||
|
@ -162,30 +160,25 @@ func authCallbackV2(c *gin.Context) {
|
|||
state := queryStr(c, "state", "")
|
||||
redirect := queryStr(c, "redirect", "")
|
||||
|
||||
ret := &authRedirect{Redirect: redirect}
|
||||
if code == "" && redirect != "" {
|
||||
logger.Debugf("sso.callback() can't get code and redirect is not set")
|
||||
renderData(c, ret, nil)
|
||||
renderData(c, &ssoc.CallbackOutput{Redirect: redirect}, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
ret.Redirect, ret.User, err = ssoc.Callback(code, state)
|
||||
ret, err := ssoc.Callback(code, state)
|
||||
logger.Debugf("sso.callback() ret %s error %v", ret, err)
|
||||
if err != nil {
|
||||
logger.Debugf("sso.callback() error %s", err)
|
||||
renderData(c, ret, err)
|
||||
renderData(c, nil, err)
|
||||
return
|
||||
}
|
||||
|
||||
if redirect := auth.ChangePasswordRedirect(ret.User, ret.Redirect); redirect != "" {
|
||||
logger.Debugf("sso.callback() redirect to changePassword %s", redirect)
|
||||
ret.Redirect = redirect
|
||||
renderData(c, ret, nil)
|
||||
return
|
||||
if err = auth.PostCallback(ret); err == nil {
|
||||
logger.Debugf("sso.callback() successfully, set username %s", ret.User.Username)
|
||||
sessionLogin(c, ret.User.Username, c.ClientIP(), ret.AccessToken)
|
||||
} else {
|
||||
logger.Debugf("sso.callback() redirect to changePassword %s", ret.Redirect)
|
||||
}
|
||||
|
||||
logger.Debugf("sso.callback() successfully, set username %s", ret.User.Username)
|
||||
sessionLogin(c, ret.User.Username, c.ClientIP())
|
||||
renderData(c, ret, nil)
|
||||
}
|
||||
|
||||
|
@ -283,10 +276,6 @@ func authLogin(in *v1LoginInput) (user *models.User, err error) {
|
|||
if err = in.Validate(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth.WhiteListAccess(in.RemoteAddr); err != nil {
|
||||
return nil, _e("Deny Access from %s with whitelist control", in.RemoteAddr)
|
||||
}
|
||||
defer func() {
|
||||
models.LoginLogNew(in.Args[0], in.RemoteAddr, "in", err)
|
||||
}()
|
||||
|
@ -304,6 +293,12 @@ func authLogin(in *v1LoginInput) (user *models.User, err error) {
|
|||
err = _e("Invalid login type %s", in.Type)
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
if err := auth.WhiteListAccess(user, in.RemoteAddr); err != nil {
|
||||
return nil, _e("Deny Access from %s with whitelist control", in.RemoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if err = auth.PostLogin(user, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -387,7 +382,7 @@ func sendLoginCode(c *gin.Context) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if debug {
|
||||
if config.Config.Auth.ExtraMode.Debug {
|
||||
return fmt.Sprintf("[debug]: %s", buf.String()), nil
|
||||
}
|
||||
|
||||
|
@ -459,7 +454,7 @@ func sendRstCode(c *gin.Context) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
if debug {
|
||||
if config.Config.Auth.ExtraMode.Debug {
|
||||
return fmt.Sprintf("[debug] msg: %s", buf.String()), nil
|
||||
}
|
||||
|
||||
|
@ -684,19 +679,63 @@ func whiteListDel(c *gin.Context) {
|
|||
}
|
||||
|
||||
func v1SessionGet(c *gin.Context) {
|
||||
sess, err := models.SessionGetWithCache(urlParamStr(c, "sid"))
|
||||
renderData(c, sess, err)
|
||||
s, err := models.SessionGetWithCache(urlParamStr(c, "sid"))
|
||||
renderData(c, s, err)
|
||||
}
|
||||
|
||||
func v1SessionGetUser(c *gin.Context) {
|
||||
user, err := models.SessionGetUserWithCache(urlParamStr(c, "sid"))
|
||||
sid := urlParamStr(c, "sid")
|
||||
|
||||
user, err := func() (*models.User, error) {
|
||||
s, err := models.SessionGetWithCache(sid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if s.Username == "" {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
return models.UserMustGet("username=?", s.Username)
|
||||
}()
|
||||
|
||||
renderData(c, user, err)
|
||||
}
|
||||
|
||||
func v1SessionDelete(c *gin.Context) {
|
||||
sid := urlParamStr(c, "sid")
|
||||
logger.Debugf("session del sid %s", sid)
|
||||
renderMessage(c, models.SessionDel(sid))
|
||||
renderMessage(c, auth.DeleteSession(sid))
|
||||
}
|
||||
|
||||
func v1TokenGet(c *gin.Context) {
|
||||
t, err := models.TokenGetWithCache(urlParamStr(c, "token"))
|
||||
renderData(c, t, err)
|
||||
}
|
||||
|
||||
func v1TokenGetUser(c *gin.Context) {
|
||||
token := urlParamStr(c, "token")
|
||||
|
||||
user, err := func() (*models.User, error) {
|
||||
t, err := models.TokenGetWithCache(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if t.Username == "" {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
return models.UserMustGet("username=?", t.Username)
|
||||
}()
|
||||
|
||||
renderData(c, user, err)
|
||||
}
|
||||
|
||||
// just for auth.extraMode
|
||||
func v1TokenDelete(c *gin.Context) {
|
||||
token := urlParamStr(c, "token")
|
||||
logger.Debugf("del token %s", token)
|
||||
|
||||
renderMessage(c, auth.DeleteToken(token))
|
||||
}
|
||||
|
||||
// pwdRulesGet return pwd rules
|
||||
|
@ -704,3 +743,11 @@ func pwdRulesGet(c *gin.Context) {
|
|||
cf := cache.AuthConfig()
|
||||
renderData(c, cf.PwdRules(), nil)
|
||||
}
|
||||
|
||||
func sessionDestory(c *gin.Context) (sid string, err error) {
|
||||
if sid, err = session.Destroy(c.Writer, c.Request); sid != "" {
|
||||
auth.DeleteSession(sid)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
@ -62,6 +62,10 @@ func main() {
|
|||
|
||||
// 初始化数据库和相关数据
|
||||
models.InitMySQL("rdb", "hbs")
|
||||
|
||||
if config.Config.SSO.Enable && config.Config.Auth.ExtraMode.Enable {
|
||||
models.InitMySQL("sso")
|
||||
}
|
||||
models.InitSalt()
|
||||
models.InitRooter()
|
||||
|
||||
|
|
|
@ -217,6 +217,8 @@ func (p *SessionStore) Set(k, v string) error {
|
|||
p.session.Username = v
|
||||
case "remoteAddr":
|
||||
p.session.RemoteAddr = v
|
||||
case "accessToken":
|
||||
p.session.AccessToken = v
|
||||
default:
|
||||
fmt.Errorf("unsupported session field %s", k)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/didi/nightingale/src/models"
|
||||
"github.com/didi/nightingale/src/modules/rdb/cache"
|
||||
"github.com/didi/nightingale/src/modules/rdb/config"
|
||||
"github.com/toolkits/pkg/logger"
|
||||
)
|
||||
|
@ -20,20 +19,13 @@ func newDbStorage(cf *config.SessionSection, opts *options) (storage, error) {
|
|||
case <-opts.ctx.Done():
|
||||
return
|
||||
case <-t.C:
|
||||
if st := cache.AuthConfig().MaxConnIdelTime * 60; st > 0 {
|
||||
err := models.SessionCleanup(time.Now().Unix() - st)
|
||||
if err != nil {
|
||||
logger.Errorf("session gc err %s", err)
|
||||
}
|
||||
} else {
|
||||
ct := config.Config.HTTP.Session.CookieLifetime
|
||||
if ct == 0 {
|
||||
ct = 86400
|
||||
err := models.SessionCleanupByCreatedAt(time.Now().Unix() - ct)
|
||||
if err != nil {
|
||||
logger.Errorf("session gc err %s", err)
|
||||
}
|
||||
}
|
||||
ct := config.Config.HTTP.Session.CookieLifetime
|
||||
if ct == 0 {
|
||||
ct = 86400
|
||||
}
|
||||
err := models.SessionCleanupByCreatedAt(time.Now().Unix() - ct)
|
||||
if err != nil {
|
||||
logger.Errorf("session gc err %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -60,12 +52,12 @@ func (p *dbStorage) get(sid string) (*models.Session, error) {
|
|||
}
|
||||
|
||||
func (p *dbStorage) insert(s *models.Session) error {
|
||||
return models.SessionInsert(s)
|
||||
return s.Save()
|
||||
|
||||
}
|
||||
|
||||
func (p *dbStorage) del(sid string) error {
|
||||
return models.SessionDel(sid)
|
||||
return models.SessionDelete(sid)
|
||||
}
|
||||
|
||||
func (p *dbStorage) update(s *models.Session) error {
|
||||
|
|
|
@ -3,6 +3,7 @@ package ssoc
|
|||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -107,40 +108,58 @@ func LogoutLocation(redirect string) string {
|
|||
url.QueryEscape(redirect))
|
||||
}
|
||||
|
||||
// Callback 用 code 兑换 accessToken 以及 用户信息,
|
||||
func Callback(code, state string) (string, *models.User, error) {
|
||||
s, err := models.AuthStateGet("state=?", state)
|
||||
if err != nil {
|
||||
return "", nil, errState
|
||||
}
|
||||
|
||||
s.Del()
|
||||
|
||||
u, err := exchangeUser(code)
|
||||
if err != nil {
|
||||
return "", nil, errUser
|
||||
}
|
||||
|
||||
user, err := models.UserGet("username=?", u.Username)
|
||||
if err != nil {
|
||||
return "", nil, errUser
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
user = u
|
||||
err = user.Save()
|
||||
} else if cli.coverAttributes {
|
||||
user.Email = u.Email
|
||||
user.Dispname = u.Dispname
|
||||
user.Phone = u.Phone
|
||||
user.Im = u.Im
|
||||
err = user.Update("email", "dispname", "phone", "im")
|
||||
}
|
||||
|
||||
return s.Redirect, user, err
|
||||
type CallbackOutput struct {
|
||||
Redirect string `json:"redirect"`
|
||||
AccessToken string `json:"accessToken"`
|
||||
User *models.User `json:"user"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
func exchangeUser(code string) (*models.User, error) {
|
||||
func (p CallbackOutput) String() string {
|
||||
b, _ := json.Marshal(p)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Callback 用 code 兑换 accessToken 以及 用户信息,
|
||||
func Callback(code, state string) (*CallbackOutput, error) {
|
||||
s, err := models.AuthStateGet("state=?", state)
|
||||
if err != nil {
|
||||
return nil, errState
|
||||
}
|
||||
s.Del()
|
||||
|
||||
ret, err := exchangeUser(code)
|
||||
if err != nil {
|
||||
return nil, errUser
|
||||
}
|
||||
ret.Redirect = s.Redirect
|
||||
|
||||
user, err := models.UserGet("username=?", ret.User.Username)
|
||||
if err != nil {
|
||||
return nil, errUser
|
||||
}
|
||||
|
||||
if user != nil {
|
||||
// user exists
|
||||
if cli.coverAttributes {
|
||||
user.Email = ret.User.Email
|
||||
user.Dispname = ret.User.Dispname
|
||||
user.Phone = ret.User.Phone
|
||||
user.Im = ret.User.Im
|
||||
user.Update("email", "dispname", "phone", "im")
|
||||
}
|
||||
ret.User = user
|
||||
} else {
|
||||
// create user from sso
|
||||
if err := ret.User.Save(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func exchangeUser(code string) (*CallbackOutput, error) {
|
||||
ctx := context.Background()
|
||||
oauth2Token, err := cli.config.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
|
@ -169,13 +188,15 @@ func exchangeUser(code string) (*models.User, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return &models.User{
|
||||
Username: v(cli.attributes.username),
|
||||
Dispname: v(cli.attributes.dispname),
|
||||
Phone: v(cli.attributes.phone),
|
||||
Email: v(cli.attributes.email),
|
||||
Im: v(cli.attributes.im),
|
||||
}, nil
|
||||
return &CallbackOutput{
|
||||
AccessToken: oauth2Token.AccessToken,
|
||||
User: &models.User{
|
||||
Username: v(cli.attributes.username),
|
||||
Dispname: v(cli.attributes.dispname),
|
||||
Phone: v(cli.attributes.phone),
|
||||
Email: v(cli.attributes.email),
|
||||
Im: v(cli.attributes.im),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func CreateClient(w http.ResponseWriter, body io.ReadCloser) error {
|
||||
|
|
Loading…
Reference in New Issue