Skip to content

Commit

Permalink
feat: add record (aka request logs) list and edit pages (#27)
Browse files Browse the repository at this point in the history
* feat: add record (aka request logs) list and edit pages

* feat: add record (aka request logs) list and edit pages

* Add AddRecord()

* refactor BaseListPage

* Resolved code inconsistency

* Update record.go

---------

Co-authored-by: HeartLinked <lifeiyang@zju.edu.cn>
Co-authored-by: Gucheng <85475922+nomeguy@users.noreply.github.com>
  • Loading branch information
3 people authored May 24, 2024
1 parent 7162aa2 commit df2e76b
Show file tree
Hide file tree
Showing 12 changed files with 814 additions and 71 deletions.
108 changes: 108 additions & 0 deletions controllers/record.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright 2024 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/casbin/caswaf/object"
)

func (c *ApiController) GetRecords() {
if c.RequireSignedIn() {
return
}

owner := c.Input().Get("owner")
if owner == "admin" {
owner = ""
}

sites, err := object.GetRecords(owner)
if err != nil {
c.ResponseError(err.Error())
return
}

// object.GetMaskedSites(sites, util.GetHostname())
c.ResponseOk(sites)
}

func (c *ApiController) DeleteRecord() {
if c.RequireSignedIn() {
return
}

var record object.Record
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
c.ResponseError(err.Error())
return
}

c.Data["json"] = wrapActionResponse(object.DeleteRecord(&record))
c.ServeJSON()
}

func (c *ApiController) UpdateRecord() {
if c.RequireSignedIn() {
return
}

owner := c.Input().Get("owner")
id := c.Input().Get("id")

var record object.Record
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
c.ResponseError(err.Error())
return
}

c.Data["json"] = wrapActionResponse(object.UpdateRecord(owner, id, &record))
c.ServeJSON()
}

func (c *ApiController) GetRecord() {
if c.RequireSignedIn() {
return
}

owner := c.Input().Get("owner")
id := c.Input().Get("id")
record, err := object.GetRecord(owner, id)
if err != nil {
c.ResponseError(err.Error())
return
}

c.ResponseOk(record)
}

func (c *ApiController) AddRecord() {
if c.RequireSignedIn() {
return
}

var record object.Record
err := json.Unmarshal(c.Ctx.Input.RequestBody, &record)
if err != nil {
c.ResponseError(err.Error())
return
}

c.Data["json"] = wrapActionResponse(object.AddRecord(&record))
c.ServeJSON()
}
5 changes: 5 additions & 0 deletions object/ormer.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,9 @@ func (a *Ormer) createTable() {
if err != nil {
panic(err)
}

err = a.Engine.Sync2(new(Record))
if err != nil {
panic(err)
}
}
97 changes: 97 additions & 0 deletions object/record.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2024 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 (
"strconv"

"github.com/xorm-io/core"
)

type Record struct {
Id int64 `xorm:"int notnull pk autoincr" json:"id"`
Owner string `xorm:"varchar(100) notnull" json:"owner"`
CreatedTime string `xorm:"varchar(100) notnull" json:"createdTime"`

Method string `xorm:"varchar(100)" json:"method"`
Host string `xorm:"varchar(100)" json:"host"`
Path string `xorm:"varchar(100)" json:"path"`
ClientIp string `xorm:"varchar(100)" json:"clientIp"`
UserAgent string `xorm:"varchar(512)" json:"userAgent"`
}

func GetRecords(owner string) ([]*Record, error) {
records := []*Record{}
err := ormer.Engine.Asc("id").Asc("host").Find(&records, &Record{Owner: owner})
if err != nil {
return nil, err
}

return records, nil
}

func AddRecord(record *Record) (bool, error) {
affected, err := ormer.Engine.Insert(record)
if err != nil {
return false, err
}

return affected != 0, nil
}

func DeleteRecord(record *Record) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{record.Id}).Delete(&Record{})
if err != nil {
return false, err
}

return affected != 0, nil
}

func UpdateRecord(owner string, id string, record *Record) (bool, error) {
affected, err := ormer.Engine.ID(core.PK{record.Id}).AllCols().Update(record)
if err != nil {
return false, err
}

return affected != 0, nil
}

func GetRecord(owner string, id string) (*Record, error) {
idNum, err := strconv.Atoi(id)
if err != nil {
return nil, err
}

record, err := getRecord(owner, int64(idNum))
if err != nil {
return nil, err
}

return record, nil
}

func getRecord(owner string, id int64) (*Record, error) {
record := Record{Owner: owner, Id: id}
existed, err := ormer.Engine.Get(&record)
if err != nil {
return nil, err
}

if existed {
return &record, nil
}
return nil, nil
}
7 changes: 7 additions & 0 deletions routers/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ func initAPI() {
beego.Router("/api/delete-cert", &controllers.ApiController{}, "POST:DeleteCert")

beego.Router("/api/get-applications", &controllers.ApiController{}, "GET:GetApplications")

beego.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
beego.Router("/api/get-record", &controllers.ApiController{}, "GET:GetRecord")
beego.Router("/api/delete-record", &controllers.ApiController{}, "POST:DeleteRecord")
beego.Router("/api/update-record", &controllers.ApiController{}, "POST:UpdateRecord")
beego.Router("/api/add-record", &controllers.ApiController{}, "POST:AddRecord")

}
42 changes: 38 additions & 4 deletions service/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package service
import (
"crypto/tls"
"fmt"
httptx "github.com/corazawaf/coraza/v3/http"
"net"
"net/http"
"net/http/httputil"
Expand All @@ -28,6 +27,7 @@ import (
"github.com/beego/beego"
"github.com/casbin/caswaf/object"
"github.com/casbin/caswaf/util"
httptx "github.com/corazawaf/coraza/v3/http"
)

func forwardHandler(targetUrl string, writer http.ResponseWriter, request *http.Request) {
Expand Down Expand Up @@ -66,6 +66,41 @@ func getHostNonWww(host string) string {
return res
}

func getClientIp(r *http.Request) string {
forwarded := r.Header.Get("X-Forwarded-For")
if forwarded != "" {
clientIP := strings.Split(forwarded, ",")[0]
return strings.TrimSpace(clientIP)
}

realIP := r.Header.Get("X-Real-IP")
if realIP != "" {
return realIP
}

ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return r.RemoteAddr
}
return ip
}

func logRequest(clientIp string, r *http.Request) {
if !strings.Contains(r.UserAgent(), "Uptime-Kuma") {
fmt.Printf("handleRequest: %s\t%s\t%s\t%s\t%s\n", r.RemoteAddr, r.Method, r.Host, r.RequestURI, r.UserAgent())
record := object.Record{
Owner: "admin",
CreatedTime: util.GetCurrentTime(),
Method: r.Method,
Host: r.Host,
Path: r.RequestURI,
ClientIp: clientIp,
UserAgent: r.UserAgent(),
}
object.AddRecord(&record)
}
}

func redirectToHttps(w http.ResponseWriter, r *http.Request) {
targetUrl := fmt.Sprintf("https://%s", joinPath(r.Host, r.RequestURI))
http.Redirect(w, r, targetUrl, http.StatusMovedPermanently)
Expand All @@ -82,9 +117,8 @@ func redirectToHost(w http.ResponseWriter, r *http.Request, host string) {
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
if !strings.Contains(r.UserAgent(), "Uptime-Kuma") {
fmt.Printf("handleRequest: %s\t%s\t%s\t%s\t%s\n", r.RemoteAddr, r.Method, r.Host, r.RequestURI, r.UserAgent())
}
clientIp := getClientIp(r)
logRequest(clientIp, r)

site := getSiteByDomainWithWww(r.Host)
if site == nil {
Expand Down
15 changes: 14 additions & 1 deletion web/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ import SiteEditPage from "./SiteEditPage";
import CertListPage from "./CertListPage";
import CertEditPage from "./CertEditPage";
import SigninPage from "./SigninPage";
import RecordListPage from "./RecordListPage";
import RecordEditPage from "./RecordEditPage";
import i18next from "i18next";
// import SelectLanguageBox from "./SelectLanguageBox";

const {Header, Footer} = Layout;

Expand Down Expand Up @@ -71,6 +72,8 @@ class App extends Component {
this.setState({selectedMenuKey: "/sites"});
} else if (uri.includes("/certs")) {
this.setState({selectedMenuKey: "/certs"});
} else if (uri.includes("/records")) {
this.setState({selectedMenuKey: "/records"});
} else {
this.setState({selectedMenuKey: "null"});
}
Expand Down Expand Up @@ -253,6 +256,13 @@ class App extends Component {
</Menu.Item>
);

res.push(
<Menu.Item key="/records">
<Link to="/records">
{i18next.t("general:Records")}
</Link>
</Menu.Item>
);
return res;
}

Expand Down Expand Up @@ -310,6 +320,9 @@ class App extends Component {
<Route exact path="/sites/:owner/:siteName" render={(props) => this.renderSigninIfNotSignedIn(<SiteEditPage account={this.state.account} {...props} />)} />
<Route exact path="/certs" render={(props) => this.renderSigninIfNotSignedIn(<CertListPage account={this.state.account} {...props} />)} />
<Route exact path="/certs/:owner/:certName" render={(props) => this.renderSigninIfNotSignedIn(<CertEditPage account={this.state.account} {...props} />)} />

<Route exact path="/records" render={(props) => this.renderSigninIfNotSignedIn(<RecordListPage account={this.state.account} {...props} />)} />
<Route exact path="/records/:owner/:id" render={(props) => this.renderSigninIfNotSignedIn(<RecordEditPage account={this.state.account} {...props} />)} />
</Switch>
</div>
);
Expand Down
Loading

0 comments on commit df2e76b

Please sign in to comment.