diff --git a/controllers/auth.go b/controllers/auth.go index fff2b175..211cc63a 100644 --- a/controllers/auth.go +++ b/controllers/auth.go @@ -181,6 +181,12 @@ func (c *ApiController) Login() { } else { application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) resp = c.HandleLoggedIn(application, user, &form) + + record := util.Records(c.Ctx) + record.Organization = application.Organization + record.Username = user.Name + + object.AddRecord(record) } } else if form.Provider != "" { application := object.GetApplication(fmt.Sprintf("admin/%s", form.Application)) @@ -252,6 +258,12 @@ func (c *ApiController) Login() { //} resp = c.HandleLoggedIn(application, user, &form) + + record := util.Records(c.Ctx) + record.Organization = application.Organization + record.Username = user.Name + + object.AddRecord(record) } else { // Sign up via OAuth if !application.EnableSignUp { @@ -294,6 +306,12 @@ func (c *ApiController) Login() { object.LinkUserAccount(user, provider.Type, userInfo.Id) resp = c.HandleLoggedIn(application, user, &form) + + record := util.Records(c.Ctx) + record.Organization = application.Organization + record.Username = user.Name + + object.AddRecord(record) } //resp = &Response{Status: "ok", Msg: "", Data: res} } else { // form.Method != "signup" diff --git a/controllers/record.go b/controllers/record.go new file mode 100644 index 00000000..177bde19 --- /dev/null +++ b/controllers/record.go @@ -0,0 +1,46 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package controllers + +import ( + "encoding/json" + + "github.com/casdoor/casdoor/object" +) + +// @Title GetRecords +// @Description get all records +// @Success 200 {array} object.Records The Response object +// @router /get-records [get] +func (c *ApiController) GetRecords() { + c.Data["json"] = object.GetRecords() + c.ServeJSON() +} + +// @Title GetRecordsByFilter +// @Description get records by filter +// @Param body body object.Records true "filter Record message" +// @Success 200 {array} object.Records The Response object +// @router /get-records-filter [post] +func (c *ApiController) GetRecordsByFilter() { + var record object.Records + err := json.Unmarshal(c.Ctx.Input.RequestBody, &record) + if err != nil { + panic(err) + } + + c.Data["json"] = object.GetRecordsByField(&record) + c.ServeJSON() +} diff --git a/main.go b/main.go index 678b62ea..e113e4f5 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,7 @@ func main() { beego.InsertFilter("*", beego.BeforeRouter, routers.StaticFilter) beego.InsertFilter("*", beego.BeforeRouter, routers.AutoLoginFilter) beego.InsertFilter("*", beego.BeforeRouter, routers.AuthzFilter) + beego.InsertFilter("*", beego.BeforeRouter, routers.RecordMessage) beego.BConfig.WebConfig.Session.SessionName = "casdoor_session_id" beego.BConfig.WebConfig.Session.SessionProvider = "file" diff --git a/object/adapter.go b/object/adapter.go index b6dfb125..2ac7a111 100644 --- a/object/adapter.go +++ b/object/adapter.go @@ -133,4 +133,8 @@ func (a *Adapter) createTable() { if err != nil { panic(err) } + err = a.Engine.Sync2(new(Records)) + if err != nil { + panic(err) + } } diff --git a/object/record.go b/object/record.go new file mode 100644 index 00000000..3058bf7a --- /dev/null +++ b/object/record.go @@ -0,0 +1,65 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package object + +import ( + "github.com/casdoor/casdoor/util" +) + +type Records struct { + Id int `xorm:"int notnull pk autoincr" json:"id"` + Record util.Record `xorm:"extends"` +} + +func AddRecord(record *util.Record) bool { + records := new(Records) + records.Record = *record + + affected, err := adapter.Engine.Insert(records) + if err != nil { + panic(err) + } + + return affected != 0 +} + +func GetRecordCount() int { + count, err := adapter.Engine.Count(&Records{}) + if err != nil { + panic(err) + } + + return int(count) +} + +func GetRecords() []*Records { + records := []*Records{} + err := adapter.Engine.Desc("id").Find(&records) + if err != nil { + panic(err) + } + + return records +} + +func GetRecordsByField(record *Records) []*Records { + records := []*Records{} + err := adapter.Engine.Find(&records, record) + if err != nil { + panic(err) + } + + return records +} diff --git a/routers/record.go b/routers/record.go new file mode 100644 index 00000000..4cbe53c0 --- /dev/null +++ b/routers/record.go @@ -0,0 +1,70 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package routers + +import ( + "strings" + + "github.com/astaxie/beego/context" + "github.com/casdoor/casdoor/object" + "github.com/casdoor/casdoor/util" +) + +func getUser(ctx *context.Context) (username string) { + defer func() { + if r := recover(); r != nil { + username = getUserByClientIdSecret(ctx) + } + }() + + username = ctx.Input.Session("username").(string) + + if username == "" { + username = getUserByClientIdSecret(ctx) + } + + return +} + +func getUserByClientIdSecret(ctx *context.Context) string { + requestUri := ctx.Request.RequestURI + clientId := parseQuery(requestUri, "clientId") + clientSecret := parseQuery(requestUri, "clientSecret") + if len(clientId) == 0 || len(clientSecret) == 0 { + return "" + } + + app := object.GetApplicationByClientId(clientId) + if app == nil || app.ClientSecret != clientSecret { + return "" + } + return app.Organization+"/"+app.Name +} + +func RecordMessage(ctx *context.Context) { + if ctx.Request.URL.Path != "/api/login" { + user := getUser(ctx) + userinfo := strings.Split(user,"/") + if user == "" { + userinfo = append(userinfo,"") + } + record := util.Records(ctx) + record.Organization = userinfo[0] + record.Username = userinfo[1] + + object.AddRecord(record) + } +} + diff --git a/routers/router.go b/routers/router.go index f2af5723..554e9acc 100644 --- a/routers/router.go +++ b/routers/router.go @@ -84,4 +84,8 @@ func initAPI() { beego.Router("/api/add-token", &controllers.ApiController{}, "POST:AddToken") beego.Router("/api/delete-token", &controllers.ApiController{}, "POST:DeleteToken") beego.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken") + + beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords") + beego.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter") } + diff --git a/swagger/swagger.json b/swagger/swagger.json index 9ec578c6..b309313d 100644 --- a/swagger/swagger.json +++ b/swagger/swagger.json @@ -362,6 +362,65 @@ } } }, + "/api/get-default-application": { + "get": { + "tags": [ + "api" + ], + "description": "get the detail of the default application", + "operationId": "ApiController.GetDefaultApplication", + "parameters": [ + { + "in": "query", + "name": "owner", + "description": "The owner of the application.", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The Response object", + "schema": { + "$ref": "#/definitions/object.Application" + } + } + } + } + }, + "/api/get-email-and-phone": { + "post": { + "tags": [ + "api" + ], + "description": "get email and phone by username", + "operationId": "ApiController.GetEmailAndPhone", + "parameters": [ + { + "in": "formData", + "name": "username", + "description": "The username of the user", + "required": true, + "type": "string" + }, + { + "in": "formData", + "name": "organization", + "description": "The organization of the user", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The Response object", + "schema": { + "$ref": "#/definitions/controllers.Response" + } + } + } + } + }, "/api/get-global-users": { "get": { "tags": [ @@ -492,6 +551,57 @@ } } }, + "/api/get-records": { + "get": { + "tags": [ + "api" + ], + "description": "get all records", + "operationId": "ApiController.GetRecords", + "responses": { + "200": { + "description": "The Response object", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/object.Records" + } + } + } + } + } + }, + "/api/get-records-filter": { + "post": { + "tags": [ + "api" + ], + "description": "get records by filter", + "operationId": "ApiController.GetRecordsByFilter", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "filter Record message", + "required": true, + "schema": { + "$ref": "#/definitions/object.Records" + } + } + ], + "responses": { + "200": { + "description": "The Response object", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/object.Records" + } + } + } + } + } + }, "/api/get-token": { "get": { "tags": [ @@ -700,18 +810,65 @@ } } }, - "/api/register": { + "/api/set-password": { "post": { "tags": [ "api" ], - "description": "register a new user", - "operationId": "ApiController.Register", + "description": "set password", + "operationId": "ApiController.SetPassword", + "parameters": [ + { + "in": "formData", + "name": "userOwner", + "description": "The owner of the user", + "required": true, + "type": "string" + }, + { + "in": "formData", + "name": "userName", + "description": "The name of the user", + "required": true, + "type": "string" + }, + { + "in": "formData", + "name": "oldPassword", + "description": "The old password of the user", + "required": true, + "type": "string" + }, + { + "in": "formData", + "name": "newPassword", + "description": "The new password of the user", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "The Response object", + "schema": { + "$ref": "#/definitions/controllers.Response" + } + } + } + } + }, + "/api/signup": { + "post": { + "tags": [ + "api" + ], + "description": "sign up a new user", + "operationId": "ApiController.Signup", "parameters": [ { "in": "formData", "name": "username", - "description": "The username to register", + "description": "The username to sign up", "required": true, "type": "string" }, @@ -965,7 +1122,7 @@ "tags": [ "api" ], - "description": "register a new user", + "description": "upload avatar", "operationId": "ApiController.UploadAvatar", "parameters": [ { @@ -995,7 +1152,11 @@ } }, "definitions": { - "1471.0xc0003bd890.false": { + "1671.0xc00044ab10.false": { + "title": "false", + "type": "object" + }, + "1705.0xc00044ab40.false": { "title": "false", "type": "object" }, @@ -1008,7 +1169,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/1471.0xc0003bd890.false" + "$ref": "#/definitions/1671.0xc00044ab10.false" + }, + "data2": { + "$ref": "#/definitions/1705.0xc00044ab40.false" }, "msg": { "type": "string" @@ -1023,7 +1187,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/definitions/1471.0xc0003bd890.false" + "$ref": "#/definitions/1671.0xc00044ab10.false" + }, + "data2": { + "$ref": "#/definitions/1705.0xc00044ab40.false" }, "msg": { "type": "string" @@ -1037,6 +1204,9 @@ "title": "Application", "type": "object", "properties": { + "affiliationUrl": { + "type": "string" + }, "clientId": { "type": "string" }, @@ -1062,6 +1232,9 @@ "type": "integer", "format": "int64" }, + "forgetUrl": { + "type": "string" + }, "homepageUrl": { "type": "string" }, @@ -1074,19 +1247,16 @@ "organization": { "type": "string" }, + "organizationObj": { + "$ref": "#/definitions/object.Organization" + }, "owner": { "type": "string" }, - "providerObjs": { - "type": "array", - "items": { - "$ref": "#/definitions/object.Provider" - } - }, "providers": { "type": "array", "items": { - "type": "string" + "$ref": "#/definitions/object.ProviderItem" } }, "redirectUris": { @@ -1094,6 +1264,18 @@ "items": { "type": "string" } + }, + "signinUrl": { + "type": "string" + }, + "signupItems": { + "type": "array", + "items": { + "$ref": "#/definitions/object.SignupItem" + } + }, + "signupUrl": { + "type": "string" } } }, @@ -1104,15 +1286,30 @@ "createdTime": { "type": "string" }, + "defaultAvatar": { + "type": "string" + }, "displayName": { "type": "string" }, + "favicon": { + "type": "string" + }, "name": { "type": "string" }, "owner": { "type": "string" }, + "passwordSalt": { + "type": "string" + }, + "passwordType": { + "type": "string" + }, + "phonePrefix": { + "type": "string" + }, "websiteUrl": { "type": "string" } @@ -1122,32 +1319,121 @@ "title": "Provider", "type": "object", "properties": { + "appId": { + "type": "string" + }, + "category": { + "type": "string" + }, "clientId": { "type": "string" }, "clientSecret": { "type": "string" }, + "content": { + "type": "string" + }, "createdTime": { "type": "string" }, "displayName": { "type": "string" }, + "host": { + "type": "string" + }, "name": { "type": "string" }, "owner": { "type": "string" }, + "port": { + "type": "integer", + "format": "int64" + }, "providerUrl": { "type": "string" }, + "regionId": { + "type": "string" + }, + "signName": { + "type": "string" + }, + "templateCode": { + "type": "string" + }, + "title": { + "type": "string" + }, "type": { "type": "string" } } }, + "object.ProviderItem": { + "title": "ProviderItem", + "type": "object", + "properties": { + "alertType": { + "type": "string" + }, + "canSignIn": { + "type": "boolean" + }, + "canSignUp": { + "type": "boolean" + }, + "canUnlink": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "prompted": { + "type": "boolean" + }, + "provider": { + "$ref": "#/definitions/object.Provider" + } + } + }, + "object.Records": { + "title": "Records", + "type": "object", + "properties": { + "Record": { + "$ref": "#/definitions/util.Record" + }, + "id": { + "type": "integer", + "format": "int64" + } + } + }, + "object.SignupItem": { + "title": "SignupItem", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "prompted": { + "type": "boolean" + }, + "required": { + "type": "boolean" + }, + "rule": { + "type": "string" + }, + "visible": { + "type": "boolean" + } + } + }, "object.Token": { "title": "Token", "type": "object", @@ -1171,6 +1457,9 @@ "name": { "type": "string" }, + "organization": { + "type": "string" + }, "owner": { "type": "string" }, @@ -1179,6 +1468,9 @@ }, "tokenType": { "type": "string" + }, + "user": { + "type": "string" } } }, @@ -1205,6 +1497,12 @@ "title": "User", "type": "object", "properties": { + "address": { + "type": "array", + "items": { + "type": "string" + } + }, "affiliation": { "type": "string" }, @@ -1214,27 +1512,45 @@ "createdTime": { "type": "string" }, + "dingtalk": { + "type": "string" + }, "displayName": { "type": "string" }, "email": { "type": "string" }, + "facebook": { + "type": "string" + }, + "gitee": { + "type": "string" + }, "github": { "type": "string" }, "google": { "type": "string" }, + "hash": { + "type": "string" + }, "id": { "type": "string" }, "isAdmin": { "type": "boolean" }, + "isForbidden": { + "type": "boolean" + }, "isGlobalAdmin": { "type": "boolean" }, + "language": { + "type": "string" + }, "name": { "type": "string" }, @@ -1244,17 +1560,65 @@ "password": { "type": "string" }, - "passwordType": { - "type": "string" - }, "phone": { "type": "string" }, + "preHash": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "type": "string" + } + }, "qq": { "type": "string" }, + "score": { + "type": "integer", + "format": "int64" + }, + "signupApplication": { + "type": "string" + }, "tag": { "type": "string" + }, + "type": { + "type": "string" + }, + "updatedTime": { + "type": "string" + }, + "wechat": { + "type": "string" + }, + "weibo": { + "type": "string" + } + } + }, + "util.Record": { + "title": "Record", + "type": "object", + "properties": { + "ip": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "requestTime": { + "type": "string" + }, + "requestUri": { + "type": "string" + }, + "urlpath": { + "type": "string" + }, + "username": { + "type": "string" } } } diff --git a/swagger/swagger.yml b/swagger/swagger.yml index 95f6c8bb..38c0ce85 100644 --- a/swagger/swagger.yml +++ b/swagger/swagger.yml @@ -234,6 +234,45 @@ paths: type: array items: $ref: '#/definitions/object.Application' + /api/get-default-application: + get: + tags: + - api + description: get the detail of the default application + operationId: ApiController.GetDefaultApplication + parameters: + - in: query + name: owner + description: The owner of the application. + required: true + type: string + responses: + "200": + description: The Response object + schema: + $ref: '#/definitions/object.Application' + /api/get-email-and-phone: + post: + tags: + - api + description: get email and phone by username + operationId: ApiController.GetEmailAndPhone + parameters: + - in: formData + name: username + description: The username of the user + required: true + type: string + - in: formData + name: organization + description: The organization of the user + required: true + type: string + responses: + "200": + description: The Response object + schema: + $ref: '#/definitions/controllers.Response' /api/get-global-users: get: tags: @@ -319,6 +358,39 @@ paths: type: array items: $ref: '#/definitions/object.Provider' + /api/get-records: + get: + tags: + - api + description: get all records + operationId: ApiController.GetRecords + responses: + "200": + description: The Response object + schema: + type: array + items: + $ref: '#/definitions/object.Records' + /api/get-records-filter: + post: + tags: + - api + description: get records by filter + operationId: ApiController.GetRecordsByFilter + parameters: + - in: body + name: body + description: filter Record message + required: true + schema: + $ref: '#/definitions/object.Records' + responses: + "200": + description: The Response object + schema: + type: array + items: + $ref: '#/definitions/object.Records' /api/get-token: get: tags: @@ -456,16 +528,48 @@ paths: description: The Response object schema: $ref: '#/definitions/controllers.Response' - /api/register: + /api/set-password: post: tags: - api - description: register a new user - operationId: ApiController.Register + description: set password + operationId: ApiController.SetPassword + parameters: + - in: formData + name: userOwner + description: The owner of the user + required: true + type: string + - in: formData + name: userName + description: The name of the user + required: true + type: string + - in: formData + name: oldPassword + description: The old password of the user + required: true + type: string + - in: formData + name: newPassword + description: The new password of the user + required: true + type: string + responses: + "200": + description: The Response object + schema: + $ref: '#/definitions/controllers.Response' + /api/signup: + post: + tags: + - api + description: sign up a new user + operationId: ApiController.Signup parameters: - in: formData name: username - description: The username to register + description: The username to sign up required: true type: string - in: formData @@ -633,7 +737,7 @@ paths: post: tags: - api - description: register a new user + description: upload avatar operationId: ApiController.UploadAvatar parameters: - in: formData @@ -652,7 +756,10 @@ paths: schema: $ref: '#/definitions/controllers.Response' definitions: - 1471.0xc0003bd890.false: + 1671.0xc00044ab10.false: + title: "false" + type: object + 1705.0xc00044ab40.false: title: "false" type: object RequestForm: @@ -663,7 +770,9 @@ definitions: type: object properties: data: - $ref: '#/definitions/1471.0xc0003bd890.false' + $ref: '#/definitions/1671.0xc00044ab10.false' + data2: + $ref: '#/definitions/1705.0xc00044ab40.false' msg: type: string status: @@ -673,7 +782,9 @@ definitions: type: object properties: data: - $ref: '#/definitions/1471.0xc0003bd890.false' + $ref: '#/definitions/1671.0xc00044ab10.false' + data2: + $ref: '#/definitions/1705.0xc00044ab40.false' msg: type: string status: @@ -682,6 +793,8 @@ definitions: title: Application type: object properties: + affiliationUrl: + type: string clientId: type: string clientSecret: @@ -699,6 +812,8 @@ definitions: expireInHours: type: integer format: int64 + forgetUrl: + type: string homepageUrl: type: string logo: @@ -707,54 +822,130 @@ definitions: type: string organization: type: string + organizationObj: + $ref: '#/definitions/object.Organization' owner: type: string - providerObjs: - type: array - items: - $ref: '#/definitions/object.Provider' providers: type: array items: - type: string + $ref: '#/definitions/object.ProviderItem' redirectUris: type: array items: type: string + signinUrl: + type: string + signupItems: + type: array + items: + $ref: '#/definitions/object.SignupItem' + signupUrl: + type: string object.Organization: title: Organization type: object properties: createdTime: type: string + defaultAvatar: + type: string displayName: type: string + favicon: + type: string name: type: string owner: type: string + passwordSalt: + type: string + passwordType: + type: string + phonePrefix: + type: string websiteUrl: type: string object.Provider: title: Provider type: object properties: + appId: + type: string + category: + type: string clientId: type: string clientSecret: type: string + content: + type: string createdTime: type: string displayName: type: string + host: + type: string name: type: string owner: type: string + port: + type: integer + format: int64 providerUrl: type: string + regionId: + type: string + signName: + type: string + templateCode: + type: string + title: + type: string type: type: string + object.ProviderItem: + title: ProviderItem + type: object + properties: + alertType: + type: string + canSignIn: + type: boolean + canSignUp: + type: boolean + canUnlink: + type: boolean + name: + type: string + prompted: + type: boolean + provider: + $ref: '#/definitions/object.Provider' + object.Records: + title: Records + type: object + properties: + Record: + $ref: '#/definitions/util.Record' + id: + type: integer + format: int64 + object.SignupItem: + title: SignupItem + type: object + properties: + name: + type: string + prompted: + type: boolean + required: + type: boolean + rule: + type: string + visible: + type: boolean object.Token: title: Token type: object @@ -772,12 +963,16 @@ definitions: format: int64 name: type: string + organization: + type: string owner: type: string scope: type: string tokenType: type: string + user: + type: string object.TokenWrapper: title: TokenWrapper type: object @@ -795,37 +990,85 @@ definitions: title: User type: object properties: + address: + type: array + items: + type: string affiliation: type: string avatar: type: string createdTime: type: string + dingtalk: + type: string displayName: type: string email: type: string + facebook: + type: string + gitee: + type: string github: type: string google: type: string + hash: + type: string id: type: string isAdmin: type: boolean + isForbidden: + type: boolean isGlobalAdmin: type: boolean + language: + type: string name: type: string owner: type: string password: type: string - passwordType: - type: string phone: type: string + preHash: + type: string + properties: + additionalProperties: + type: string qq: type: string + score: + type: integer + format: int64 + signupApplication: + type: string tag: type: string + type: + type: string + updatedTime: + type: string + wechat: + type: string + weibo: + type: string + util.Record: + title: Record + type: object + properties: + ip: + type: string + organization: + type: string + requestTime: + type: string + requestUri: + type: string + urlpath: + type: string + username: + type: string diff --git a/util/record.go b/util/record.go new file mode 100644 index 00000000..2b0f9fcb --- /dev/null +++ b/util/record.go @@ -0,0 +1,47 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "strings" + + "github.com/astaxie/beego/context" +) + +type Record struct { + ClientIp string `xorm:"varchar(100)" json:"clientIp"` + Timestamp string `xorm:"varchar(100)" json:"timestamp"` + Organization string `xorm:"varchar(100)" json:"organization"` + Username string `xorm:"varchar(100)" json:"username"` + RequestUri string `xorm:"varchar(1000)" json:"requestUri"` + Action string `xorm:"varchar(1000)" json:"action"` +} + +func Records(ctx *context.Context) *Record { + ip := strings.Replace(getIPFromRequest(ctx.Request), ": ", "", -1) + currenttime := GetCurrentTime() + requesturi := ctx.Request.RequestURI + action := strings.Replace(ctx.Request.URL.Path, "/api/", "", -1) + + record := Record{ + ClientIp: ip, + Timestamp: currenttime, + RequestUri: requesturi, + Username: "", + Organization: "", + Action: action, + } + return &record +} diff --git a/web/src/App.js b/web/src/App.js index 7e7aaa95..f80d0520 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -29,6 +29,7 @@ import ApplicationListPage from "./ApplicationListPage"; import ApplicationEditPage from "./ApplicationEditPage"; import TokenListPage from "./TokenListPage"; import TokenEditPage from "./TokenEditPage"; +import RecordListPage from "./RecordListPage"; import AccountPage from "./account/AccountPage"; import HomePage from "./basic/HomePage"; import CustomGithubCorner from "./CustomGithubCorner"; @@ -317,6 +318,13 @@ class App extends Component { ); + res.push( + + + {i18next.t("general:Records")} + + + ); } res.push( window.location.href = "/swagger"}> @@ -392,6 +400,7 @@ class App extends Component { this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> this.renderLoginIfNotLoggedIn()}/> + this.renderLoginIfNotLoggedIn()}/> ) diff --git a/web/src/RecordListPage.js b/web/src/RecordListPage.js new file mode 100644 index 00000000..e52c18d8 --- /dev/null +++ b/web/src/RecordListPage.js @@ -0,0 +1,159 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import {Link} from "react-router-dom"; +import {Col, Row, Table} from 'antd'; +import * as Setting from "./Setting"; +import * as RecordBackend from "./backend/RecordBackend"; +import i18next from "i18next"; + +class RecordListPage extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + records: null, + }; + } + + UNSAFE_componentWillMount() { + this.getRecords(); + } + + getRecords() { + RecordBackend.getRecords() + .then((res) => { + this.setState({ + records: res, + }); + }); + } + + newRecord() { + return { + id : "", + Record:{ + clientIp:"", + timestamp:"", + organization:"", + username:"", + requestUri:"", + action:"login", + }, + } + } + + renderTable(records) { + const columns = [ + { + title: i18next.t("general:Client ip"), + dataIndex: 'Record', + key: 'Record', + width: '120px', + sorter: (a, b) => a.Record.clientIp.localeCompare(b.Record.clientIp), + render: (text, record, index) => { + return text.clientIp; + } + }, + { + title: i18next.t("general:Timestamp"), + dataIndex: 'Record', + key: 'Record', + width: '160px', + sorter: (a, b) => a.Record.timestamp.localeCompare(b.Record.timestamp), + render: (text, record, index) => { + return Setting.getFormattedDate(text.timestamp); + } + }, + { + title: i18next.t("general:Organization"), + dataIndex: 'Record', + key: 'Record', + width: '120px', + sorter: (a, b) => a.Record.organization.localeCompare(b.Record.organization), + render: (text, record, index) => { + return ( + + {text.organization} + + ) + } + }, + { + title: i18next.t("general:Username"), + dataIndex: 'Record', + key: 'Record', + width: '160px', + sorter: (a, b) => a.Record.username.localeCompare(b.Record.username), + render: (text, record, index) => { + return text.username; + } + }, + { + title: i18next.t("general:Request uri"), + dataIndex: 'Record', + key: 'Record', + width: '160px', + sorter: (a, b) => a.Record.requestUri.localeCompare(b.Record.requestUri), + render: (text, record, index) => { + return text.requestUri; + } + }, + { + title: i18next.t("general:Action"), + dataIndex: 'Record', + key: 'Record', + width: '160px', + sorter: (a, b) => a.Record.action.localeCompare(b.Record.action), + render: (text, record, index) => { + return text.action; + } + }, + ]; + + return ( +
+ ( +
+ {i18next.t("general:Records")}     +
+ )} + loading={records === null} + /> + + ); + } + + render() { + return ( +
+ +
+ + + { + this.renderTable(this.state.records) + } + + + + + + ); + } +} + +export default RecordListPage; diff --git a/web/src/backend/RecordBackend.js b/web/src/backend/RecordBackend.js new file mode 100644 index 00000000..873eee67 --- /dev/null +++ b/web/src/backend/RecordBackend.js @@ -0,0 +1,22 @@ +// Copyright 2021 The casbin Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as Setting from "../Setting"; + +export function getRecords() { + return fetch(`${Setting.ServerUrl}/api/get-records`, { + method: "GET", + credentials: "include" + }).then(res => res.json()); +} diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index 25be9ee9..92a5833b 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -13,6 +13,11 @@ "User": "User", "Applications": "Applications", "Application": "Application", + "Records": "Records", + "Client ip": "Client ip", + "Timestamp": "Timestamp", + "Username": "Username", + "Request uri": "Request uri", "Save": "Save", "Add": "Add", "Action": "Action",