Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: limit one user(not admin) session for one application by casdoor #43

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
7 changes: 4 additions & 3 deletions controllers/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func (c *ApiController) Signin() {
func (c *ApiController) Signout() {
claims := c.GetSessionClaims()
if claims != nil {
clearUserDuplicated(claims)
DeleteSession(claims.Name)
}

c.SetSessionClaims(nil)
Expand All @@ -77,12 +77,13 @@ func (c *ApiController) GetAccount() {
}

claims := c.GetSessionClaims()

if isUserDuplicated(claims) {
if IsSessionDuplicated(claims.Name, c.Ctx.Input.CruSession.SessionID()) {
if !claims.IsAdmin {
c.ResponseError("you have signed in from another place, this session has been ended")
return
}
} else {
AddSession(claims.Name, c.Ctx.Input.CruSession.SessionID())
}

c.ResponseOk(claims)
Expand Down
231 changes: 216 additions & 15 deletions controllers/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,31 +15,232 @@
package controllers

import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"

"github.com/astaxie/beego"
"github.com/casdoor/casdoor-go-sdk/auth"
)

var sessionMap = map[string]int64{}
type Session struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of code should be put into casdoor-go-sdk

Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
Application string `xorm:"varchar(100) notnull pk" json:"application"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`

func clearUserDuplicated(claims *auth.Claims) {
userId := fmt.Sprintf("%s/%s", claims.Owner, claims.Name)
delete(sessionMap, userId)
SessionId []string `json:"sessionId"`
}

func isUserDuplicated(claims *auth.Claims) bool {
userId := fmt.Sprintf("%s/%s", claims.Owner, claims.Name)
unixTimestamp := claims.IssuedAt.Unix()
func GetSessions() ([]*Session, error) {
queryMap := map[string]string{
"owner": getOrg(),
}

url := auth.GetUrl("get-sessions", queryMap)

bytes, err := auth.DoGetBytesRaw(url)
if err != nil {
return nil, err
}

var sessions []*Session
err = json.Unmarshal(bytes, &sessions)
if err != nil {
return nil, err
}
return sessions, nil
}

func GetSession(userName string) (*Session, error) {
queryMap := map[string]string{
"sessionPkId": fmt.Sprintf("%s/%s/%s", getOrg(), userName, getApp()),
}

url := auth.GetUrl("get-session", queryMap)

bytes, err := auth.DoGetBytesRaw(url)
if err != nil {
return nil, err
}

var session *Session
err = json.Unmarshal(bytes, &session)
if err != nil {
return nil, err
}
return session, nil
}

func UpdateSession(userName string, sessionId string) (bool, error) {
session := &Session{
Owner: getOrg(),
Name: userName,
Application: getApp(),
SessionId: []string{sessionId},
}

postBytes, _ := json.Marshal(session)

resp, err := doPost("update-session", nil, postBytes, false)

if err != nil {
return false, err
}

return resp.Data == "Affected", nil
}

func AddSession(userName string, sessionId string) (bool, error) {
session := &Session{
Owner: getOrg(),
Name: userName,
Application: getApp(),
SessionId: []string{sessionId},
}

postBytes, _ := json.Marshal(session)

resp, err := doPost("add-session", nil, postBytes, false)

if err != nil {
return false, err
}

return resp.Data == "Affected", nil
}

func DeleteSession(userName string) (bool, error) {
session := &Session{
Owner: getOrg(),
Name: userName,
Application: getApp(),
}

postBytes, _ := json.Marshal(session)

resp, err := doPost("delete-session", nil, postBytes, false)

if err != nil {
return false, err
}

return resp.Data == "Affected", nil
}

sessionUnixTimestamp, ok := sessionMap[userId]
if !ok {
sessionMap[userId] = unixTimestamp
return false
func IsSessionDuplicated(userName string, sessionId string) bool {
queryMap := map[string]string{
"sessionPkId": fmt.Sprintf("%s/%s/%s", getOrg(), userName, getApp()),
"sessionId": sessionId,
}

url := auth.GetUrl("is-session-duplicated", queryMap)

resp, _ := doGetResponse(url)

return resp.Data == true
}

func getOrg() string {
return beego.AppConfig.String("casdoorOrganization")
}

func getApp() string {
return beego.AppConfig.String("casdoorApplication")
}

func doPost(action string, queryMap map[string]string, postBytes []byte, isFile bool) (*Response, error) {
client := &http.Client{}
url := auth.GetUrl(action, queryMap)

var resp *http.Response
var err error
var contentType string
var body io.Reader
if isFile {
contentType, body, err = createForm(map[string][]byte{"file": postBytes})
if err != nil {
return nil, err
}
} else {
if unixTimestamp == sessionUnixTimestamp {
return false
} else {
return true
contentType = "text/plain;charset=UTF-8"
body = bytes.NewReader(postBytes)
}

req, err := http.NewRequest("POST", url, body)
if err != nil {
return nil, err
}

clientId := beego.AppConfig.String("clientId")
clientSecret := beego.AppConfig.String("clientSecret")

req.SetBasicAuth(clientId, clientSecret)
req.Header.Set("Content-Type", contentType)

resp, err = client.Do(req)
if err != nil {
return nil, err
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
return
}
}(resp.Body)

respByte, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

var response Response
err = json.Unmarshal(respByte, &response)
if err != nil {
return nil, err
}

return &response, nil
}

func createForm(formData map[string][]byte) (string, io.Reader, error) {
// https://tonybai.com/2021/01/16/upload-and-download-file-using-multipart-form-over-http/

body := new(bytes.Buffer)
w := multipart.NewWriter(body)
defer w.Close()

for k, v := range formData {
pw, err := w.CreateFormFile(k, "file")
if err != nil {
panic(err)
}

_, err = pw.Write(v)
if err != nil {
panic(err)
}
}

return w.FormDataContentType(), body, nil
}

func doGetResponse(url string) (*Response, error) {
respBytes, err := auth.DoGetBytesRaw(url)

var response Response
err = json.Unmarshal(respBytes, &response)
if err != nil {
return nil, err
}

if response.Status != "ok" {
return nil, fmt.Errorf(response.Msg)
}

return &response, nil
}