bilibili-backup/library/net/http/blademaster/middleware/permit/permit.go
2019-04-22 02:59:20 +00:00

322 lines
8.1 KiB
Go

package permit
import (
"net/url"
mng "go-common/app/admin/main/manager/api"
"go-common/library/ecode"
"go-common/library/log"
bm "go-common/library/net/http/blademaster"
"go-common/library/net/metadata"
"go-common/library/net/rpc/warden"
"github.com/pkg/errors"
)
const (
_verifyURI = "/api/session/verify"
_permissionURI = "/x/admin/manager/permission"
_sessIDKey = "_AJSESSIONID"
_sessUIDKey = "uid" // manager user_id
_sessUnKey = "username" // LDAP username
_defaultDomain = ".bilibili.co"
_defaultCookieName = "mng-go"
_defaultCookieLifeTime = 2592000
// CtxPermissions will be set into ctx.
CtxPermissions = "permissions"
)
// permissions .
type permissions struct {
UID int64 `json:"uid"`
Perms []string `json:"perms"`
}
// Permit is an auth middleware.
type Permit struct {
verifyURI string
permissionURI string
dashboardCaller string
dsClient *bm.Client // dashboard client
maClient *bm.Client // manager-admin client
sm *SessionManager // user Session
mng.PermitClient // mng grpc client
}
//Verify only export Verify function because of less configure
type Verify interface {
Verify() bm.HandlerFunc
}
// Config identify config.
type Config struct {
DsHTTPClient *bm.ClientConfig // dashboard client config. appkey can not reuse.
MaHTTPClient *bm.ClientConfig // manager-admin client config
Session *SessionConfig
ManagerHost string
DashboardHost string
DashboardCaller string
}
// Config2 .
type Config2 struct {
MngClient *warden.ClientConfig
Session *SessionConfig
}
// New new an auth service.
func New(c *Config) *Permit {
a := &Permit{
dashboardCaller: c.DashboardCaller,
verifyURI: c.DashboardHost + _verifyURI,
permissionURI: c.ManagerHost + _permissionURI,
dsClient: bm.NewClient(c.DsHTTPClient),
maClient: bm.NewClient(c.MaHTTPClient),
sm: newSessionManager(c.Session),
}
return a
}
// New2 .
func New2(c *warden.ClientConfig) *Permit {
permitClient, err := mng.NewClient(c)
if err != nil {
panic(errors.WithMessage(err, "Failed to dial mng rpc server"))
}
return &Permit{
PermitClient: permitClient,
sm: &SessionManager{},
}
}
// NewVerify new a verify service.
func NewVerify(c *Config) Verify {
a := &Permit{
verifyURI: c.DashboardHost + _verifyURI,
dsClient: bm.NewClient(c.DsHTTPClient),
dashboardCaller: c.DashboardCaller,
sm: newSessionManager(c.Session),
}
return a
}
// Verify2 check whether the user has logged in.
func (p *Permit) Verify2() bm.HandlerFunc {
return func(ctx *bm.Context) {
sid, username, err := p.login2(ctx)
if err != nil {
ctx.JSON(nil, ecode.Unauthorized)
ctx.Abort()
return
}
ctx.Set(_sessUnKey, username)
p.sm.setHTTPCookie(ctx, _defaultCookieName, sid)
}
}
// Verify return bm HandlerFunc which check whether the user has logged in.
func (p *Permit) Verify() bm.HandlerFunc {
return func(ctx *bm.Context) {
si, err := p.login(ctx)
if err != nil {
ctx.JSON(nil, ecode.Unauthorized)
ctx.Abort()
return
}
p.sm.SessionRelease(ctx, si)
}
}
// Permit return bm HandlerFunc which check whether the user has logged in and has the access permission of the location.
// If `permit` is empty,it will allow any logged in request.
func (p *Permit) Permit(permit string) bm.HandlerFunc {
return func(ctx *bm.Context) {
var (
si *Session
uid int64
perms []string
err error
)
si, err = p.login(ctx)
if err != nil {
ctx.JSON(nil, ecode.Unauthorized)
ctx.Abort()
return
}
defer p.sm.SessionRelease(ctx, si)
uid, perms, err = p.permissions(ctx, si.Get(_sessUnKey).(string))
if err == nil {
si.Set(_sessUIDKey, uid)
ctx.Set(_sessUIDKey, uid)
if md, ok := metadata.FromContext(ctx); ok {
md[metadata.Uid] = uid
}
}
if len(perms) > 0 {
ctx.Set(CtxPermissions, perms)
}
if !p.permit(permit, perms) {
ctx.JSON(nil, ecode.AccessDenied)
ctx.Abort()
return
}
}
}
// login check whether the user has logged in.
func (p *Permit) login(ctx *bm.Context) (si *Session, err error) {
si = p.sm.SessionStart(ctx)
if si.Get(_sessUnKey) == nil {
var username string
if username, err = p.verify(ctx); err != nil {
return
}
si.Set(_sessUnKey, username)
}
ctx.Set(_sessUnKey, si.Get(_sessUnKey))
if md, ok := metadata.FromContext(ctx); ok {
md[metadata.Username] = si.Get(_sessUnKey)
}
return
}
// Permit2 same function as permit function but reply on grpc.
func (p *Permit) Permit2(permit string) bm.HandlerFunc {
return func(ctx *bm.Context) {
sid, username, err := p.login2(ctx)
if err != nil {
ctx.JSON(nil, ecode.Unauthorized)
ctx.Abort()
return
}
p.sm.setHTTPCookie(ctx, _defaultCookieName, sid)
ctx.Set(_sessUnKey, username)
if md, ok := metadata.FromContext(ctx); ok {
md[metadata.Username] = username
}
reply, err := p.Permissions(ctx, &mng.PermissionReq{Username: username})
if err != nil {
if ecode.NothingFound.Equal(err) && permit != "" {
ctx.JSON(nil, ecode.AccessDenied)
ctx.Abort()
}
return
}
ctx.Set(_sessUIDKey, reply.Uid)
if md, ok := metadata.FromContext(ctx); ok {
md[metadata.Uid] = reply.Uid
}
if len(reply.Perms) > 0 {
ctx.Set(CtxPermissions, reply.Perms)
}
if !p.permit(permit, reply.Perms) {
ctx.JSON(nil, ecode.AccessDenied)
ctx.Abort()
return
}
}
}
// login2 .
func (p *Permit) login2(ctx *bm.Context) (sid, uname string, err error) {
var dsbsid, mngsid string
dsbck, err := ctx.Request.Cookie(_sessIDKey)
if err == nil {
dsbsid = dsbck.Value
}
if dsbsid == "" {
err = ecode.Unauthorized
return
}
mngck, err := ctx.Request.Cookie(_defaultCookieName)
if err == nil {
mngsid = mngck.Value
}
reply, err := p.Login(ctx, &mng.LoginReq{Mngsid: mngsid, Dsbsid: dsbsid})
if err != nil {
log.Error("mng rpc Login error(%v)", err)
return
}
sid = reply.Sid
uname = reply.Username
return
}
func (p *Permit) verify(ctx *bm.Context) (username string, err error) {
var (
sid string
r = ctx.Request
)
session, err := r.Cookie(_sessIDKey)
if err == nil {
sid = session.Value
}
if sid == "" {
err = ecode.Unauthorized
return
}
username, err = p.verifyDashboard(ctx, sid)
return
}
// permit check whether user has the access permission of the location.
func (p *Permit) permit(permit string, permissions []string) bool {
if permit == "" {
return true
}
for _, p := range permissions {
if p == permit {
// access the permit
return true
}
}
return false
}
// verifyDashboard check whether the user is valid from Dashboard.
func (p *Permit) verifyDashboard(ctx *bm.Context, sid string) (username string, err error) {
params := url.Values{}
params.Set("session_id", sid)
params.Set("encrypt", "md5")
params.Set("caller", p.dashboardCaller)
var res struct {
Code int `json:"code"`
UserName string `json:"username"`
}
if err = p.dsClient.Get(ctx, p.verifyURI, metadata.String(ctx, metadata.RemoteIP), params, &res); err != nil {
log.Error("dashboard get verify Session url(%s) error(%v)", p.verifyURI+"?"+params.Encode(), err)
return
}
if ecode.Int(res.Code) != ecode.OK {
log.Error("dashboard get verify Session url(%s) error(%v)", p.verifyURI+"?"+params.Encode(), res.Code)
err = ecode.Int(res.Code)
return
}
username = res.UserName
return
}
// permissions get user's permisssions from manager-admin.
func (p *Permit) permissions(ctx *bm.Context, username string) (uid int64, perms []string, err error) {
params := url.Values{}
params.Set(_sessUnKey, username)
var res struct {
Code int `json:"code"`
Data permissions `json:"data"`
}
if err = p.maClient.Get(ctx, p.permissionURI, metadata.String(ctx, metadata.RemoteIP), params, &res); err != nil {
log.Error("dashboard get permissions url(%s) error(%v)", p.permissionURI+"?"+params.Encode(), err)
return
}
if ecode.Int(res.Code) != ecode.OK {
log.Error("dashboard get permissions url(%s) error(%v)", p.permissionURI+"?"+params.Encode(), res.Code)
err = ecode.Int(res.Code)
return
}
perms = res.Data.Perms
uid = res.Data.UID
return
}