diff --git a/conf/app.conf b/conf/app.conf index 94a022a8..b68bc4cb 100644 --- a/conf/app.conf +++ b/conf/app.conf @@ -20,6 +20,7 @@ staticBaseUrl = "https://cdn.casbin.org" isDemoMode = false batchSize = 100 ldapServerPort = 389 +radiusServerPort = 1812 quota = {"organization": -1, "user": -1, "application": -1, "provider": -1} logConfig = {"filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"} initDataFile = "./init_data.json" \ No newline at end of file diff --git a/go.mod b/go.mod index 213ab5c8..f9800e2a 100644 --- a/go.mod +++ b/go.mod @@ -66,6 +66,7 @@ require ( google.golang.org/api v0.138.0 gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 + layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 // indirect maunium.net/go/mautrix v0.16.0 modernc.org/sqlite v1.18.2 ) diff --git a/go.sum b/go.sum index c3e229e0..b7a65afa 100644 --- a/go.sum +++ b/go.sum @@ -1912,6 +1912,7 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -2768,6 +2769,8 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= +layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68 h1:2NDro2Jzkrqfngy/sA5GVnChs7fx8EzcQKFi/lI2cfg= +layeh.com/radius v0.0.0-20221205141417-e7fbddd11d68/go.mod h1:pFWM9De99EY9TPVyHIyA56QmoRViVck/x41WFkUlc9A= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= diff --git a/main.go b/main.go index c2765d83..a160cbd3 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ import ( "github.com/casdoor/casdoor/ldap" "github.com/casdoor/casdoor/object" "github.com/casdoor/casdoor/proxy" + "github.com/casdoor/casdoor/radius" "github.com/casdoor/casdoor/routers" "github.com/casdoor/casdoor/util" ) @@ -81,6 +82,7 @@ func main() { logs.SetLogFuncCall(false) go ldap.StartLdapServer() + go radius.StartRadiusServer() go object.ClearThroughputPerSecond() beego.Run(fmt.Sprintf(":%v", port)) diff --git a/radius/server.go b/radius/server.go new file mode 100644 index 00000000..086a9b4e --- /dev/null +++ b/radius/server.go @@ -0,0 +1,67 @@ +// Copyright 2023 The Casdoor 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 radius + +import ( + "log" + + "github.com/casdoor/casdoor/conf" + "github.com/casdoor/casdoor/object" + "layeh.com/radius" + "layeh.com/radius/rfc2865" +) + +// https://support.huawei.com/enterprise/zh/doc/EDOC1000178159/35071f9a#tab_3 +func StartRadiusServer() { + server := radius.PacketServer{ + Addr: "0.0.0.0:" + conf.GetConfigString("radiusServerPort"), + Handler: radius.HandlerFunc(handlerRadius), + SecretSource: radius.StaticSecretSource([]byte(`secret`)), + } + log.Printf("Starting Radius server on %s", server.Addr) + if err := server.ListenAndServe(); err != nil { + log.Fatalf("StartRadiusServer() failed, err = %v", err) + } +} + +func handlerRadius(w radius.ResponseWriter, r *radius.Request) { + switch r.Code { + case radius.CodeAccessRequest: + handleAccessRequest(w, r) + default: + log.Printf("radius message, code = %d", r.Code) + } +} + +func handleAccessRequest(w radius.ResponseWriter, r *radius.Request) { + username := rfc2865.UserName_GetString(r.Packet) + password := rfc2865.UserPassword_GetString(r.Packet) + organization := parseOrganization(r.Packet) + code := radius.CodeAccessAccept + + log.Printf("username=%v, password=%v, code=%v, org=%v", username, password, code, organization) + if organization == "" { + code = radius.CodeAccessReject + w.Write(r.Response(code)) + return + } + _, msg := object.CheckUserPassword(organization, username, password, "en") + if msg != "" { + code = radius.CodeAccessReject + w.Write(r.Response(code)) + return + } + w.Write(r.Response(code)) +} diff --git a/radius/server_test.go b/radius/server_test.go new file mode 100644 index 00000000..d96c7380 --- /dev/null +++ b/radius/server_test.go @@ -0,0 +1,62 @@ +// Copyright 2023 The Casdoor 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. + +//go:build !skipCi +// +build !skipCi + +package radius + +import ( + "context" + "testing" + + "layeh.com/radius" + "layeh.com/radius/rfc2865" +) + +func TestAccessRequestRejected(t *testing.T) { + packet := radius.New(radius.CodeAccessRequest, []byte(`secret`)) + rfc2865.UserName_SetString(packet, "admin") + rfc2865.UserPassword_SetString(packet, "12345") + vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in")) + if err != nil { + t.Fatal(err) + } + packet.Add(rfc2865.VendorSpecific_Type, vsa) + response, err := radius.Exchange(context.Background(), packet, "localhost:1812") + if err != nil { + t.Fatal(err) + } + if response.Code != radius.CodeAccessReject { + t.Fatalf("Expected %v, got %v", radius.CodeAccessReject, response.Code) + } +} + +func TestAccessRequestAccepted(t *testing.T) { + packet := radius.New(radius.CodeAccessRequest, []byte(`secret`)) + rfc2865.UserName_SetString(packet, "admin") + rfc2865.UserPassword_SetString(packet, "123") + vsa, err := radius.NewVendorSpecific(OrganizationVendorID, []byte("built-in")) + if err != nil { + t.Fatal(err) + } + packet.Add(rfc2865.VendorSpecific_Type, vsa) + response, err := radius.Exchange(context.Background(), packet, "localhost:1812") + if err != nil { + t.Fatal(err) + } + if response.Code != radius.CodeAccessAccept { + t.Fatalf("Expected %v, got %v", radius.CodeAccessAccept, response.Code) + } +} diff --git a/radius/util.go b/radius/util.go new file mode 100644 index 00000000..203d3a08 --- /dev/null +++ b/radius/util.go @@ -0,0 +1,40 @@ +// Copyright 2023 The Casdoor 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 radius + +import ( + "layeh.com/radius" + "layeh.com/radius/rfc2865" +) + +const ( + OrganizationVendorID = uint32(100) +) + +func parseOrganization(p *radius.Packet) string { + for _, avp := range p.Attributes { + if avp.Type == rfc2865.VendorSpecific_Type { + attr := avp.Attribute + vendorId, value, err := radius.VendorSpecific(attr) + if err != nil { + return "" + } + if vendorId == OrganizationVendorID { + return string(value) + } + } + } + return "" +}