Compare commits

...

5 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
5483b40a88 Add kerberos-login to authz policy and fix kerberosLogin URL construction
Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com>
2026-03-06 13:17:15 +00:00
copilot-swe-agent[bot]
4516101db6 Add translated Kerberos error messages to all supported locales
Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com>
2026-03-06 13:09:13 +00:00
copilot-swe-agent[bot]
a849203e4b Add Kerberos/IWA backend support: organization config, SPNEGO token validation, kerberos-login endpoint
Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com>
2026-03-06 13:07:59 +00:00
copilot-swe-agent[bot]
3b4485ae24 Initial plan for Kerberos/IWA support
Co-authored-by: nomeguy <85475922+nomeguy@users.noreply.github.com>
2026-03-06 12:50:37 +00:00
copilot-swe-agent[bot]
8b78b9909c Initial plan 2026-03-06 12:43:23 +00:00
36 changed files with 483 additions and 63 deletions

View File

@@ -118,6 +118,7 @@ p, *, *, GET, /api/run-casbin-command, *, *
p, *, *, POST, /api/refresh-engines, *, *
p, *, *, GET, /api/get-invitation-info, *, *
p, *, *, GET, /api/faceid-signin-begin, *, *
p, *, *, GET, /api/kerberos-login, *, *
`
sa := stringadapter.NewAdapter(ruleText)

123
controllers/kerberos.go Normal file
View File

@@ -0,0 +1,123 @@
// Copyright 2025 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 controllers
import (
"fmt"
"net/http"
"strings"
"github.com/casdoor/casdoor/form"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/util"
)
// GetKerberosLogin
// @Title GetKerberosLogin
// @Tag Login API
// @Description perform Integrated Windows Authentication (IWA) via Kerberos SPNEGO
// @Param applicationName query string true "name of the application"
// @Param redirectUri query string false "redirect URI after successful login"
// @Success 200 {object} controllers.Response The Response object
// @router /api/kerberos-login [get]
func (c *ApiController) GetKerberosLogin() {
applicationName := c.Ctx.Input.Query("application")
responseType := c.Ctx.Input.Query("responseType")
clientId := c.Ctx.Input.Query("clientId")
redirectUri := c.Ctx.Input.Query("redirectUri")
application, err := object.GetApplication(fmt.Sprintf("admin/%s", applicationName))
if err != nil {
c.ResponseError(err.Error())
return
}
if application == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The application: %s does not exist"), applicationName))
return
}
if !application.IsKerberosEnabled() {
c.ResponseError(c.T("auth:The login method: login with Kerberos is not enabled for the application"))
return
}
// Get organization to access Kerberos config
org, err := object.GetOrganization(util.GetId("admin", application.Organization))
if err != nil {
c.ResponseError(err.Error())
return
}
if org == nil {
c.ResponseError(fmt.Sprintf(c.T("auth:The organization: %s does not exist"), application.Organization))
return
}
// Check for SPNEGO Authorization header
authHeader := c.Ctx.Request.Header.Get("Authorization")
if authHeader == "" || !strings.HasPrefix(authHeader, "Negotiate ") {
// No Kerberos token yet - send 401 challenge to trigger browser negotiation
c.Ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Negotiate")
c.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
return
}
// Extract the SPNEGO token from the Authorization header
tokenBase64 := strings.TrimPrefix(authHeader, "Negotiate ")
// Validate the Kerberos token and get the username
username, err := object.CheckKerberosToken(org, tokenBase64)
if err != nil {
c.ResponseError(fmt.Sprintf(c.T("auth:Kerberos authentication failed: %s"), err.Error()))
return
}
// Find the user in the organization
user, err := object.GetUserByFields(application.Organization, username)
if err != nil {
c.ResponseError(err.Error())
return
}
if user == nil {
c.ResponseError(fmt.Sprintf(c.T("general:The user: %s doesn't exist"), username))
return
}
// Build the AuthForm for the login flow
authForm := form.AuthForm{
Type: responseType,
Application: applicationName,
Organization: application.Organization,
Username: username,
SigninMethod: "Kerberos",
ClientId: clientId,
RedirectUri: redirectUri,
}
organization, err := object.GetOrganizationByUser(user)
if err != nil {
c.ResponseError(err.Error())
return
}
if checkMfaEnable(c, user, organization, "kerberos") {
return
}
resp := c.HandleLoggedIn(application, user, &authForm)
c.Ctx.Input.SetParam("recordUserId", user.GetId())
c.Data["json"] = resp
c.ServeJSON()
}

7
go.mod
View File

@@ -48,6 +48,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0
github.com/hsluoyz/modsecurity-go v0.0.7
github.com/jcmturner/gokrb5/v8 v8.4.4
github.com/json-iterator/go v1.1.12
github.com/lestrrat-go/jwx v1.2.29
github.com/lib/pq v1.10.9
@@ -184,8 +185,14 @@ require (
github.com/gorilla/websocket v1.5.3 // indirect
github.com/gregdel/pushover v1.3.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/golang-lru v0.5.4 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jonboulle/clockwork v0.2.2 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect

22
go.sum
View File

@@ -910,6 +910,8 @@ github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20230310173818-32f1caf87195/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI=
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc h1:OlJhrgI3I+FLUCTI3JJW8MoqyM78WbqJjecqMnqG+wc=
github.com/corazawaf/coraza-coreruleset v0.0.0-20240226094324-415b1017abdc/go.mod h1:7rsocqNDkTCira5T0M7buoKR2ehh7YZiPkzxRuAgvVU=
github.com/corazawaf/coraza/v3 v3.3.3 h1:kqjStHAgWqwP5dh7n0vhTOF0a3t+VikNS/EaMiG0Fhk=
github.com/corazawaf/coraza/v3 v3.3.3/go.mod h1:xSaXWOhFMSbrV8qOOfBKAyw3aOqfwaSaOy5BgSF8XlA=
github.com/corazawaf/libinjection-go v0.2.2 h1:Chzodvb6+NXh6wew5/yhD0Ggioif9ACrQGR4qjTCs1g=
@@ -929,7 +931,6 @@ github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs
github.com/cznic/sortutil v0.0.0-20181122101858-f5f958428db8/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ=
github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -1001,6 +1002,8 @@ github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzP
github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
@@ -1261,7 +1264,9 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1 h1:LqbZZ9sNMWVjeXS4NN5oVvhMjDyLhmA1LG86oSo+IqY=
github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@@ -1293,6 +1298,8 @@ github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdv
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
@@ -1313,11 +1320,21 @@ github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da h1:FjHUJJ7oBW4G/9
github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jcchavezs/mergefs v0.1.0 h1:7oteO7Ocl/fnfFMkoVLJxTveCjrsd//UB0j89xmnpec=
github.com/jcchavezs/mergefs v0.1.0/go.mod h1:eRLTrsA+vFwQZ48hj8p8gki/5v9C2bFtHH5Mnn4bcGk=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg=
github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc=
github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8=
github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs=
github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY=
github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
@@ -1458,7 +1475,6 @@ github.com/microsoft/go-mssqldb v1.9.0 h1:5Vq+u2f4LDujJNeZn62Z4kBDEC9MjLv0ukRzOu
github.com/microsoft/go-mssqldb v1.9.0/go.mod h1:GBbW9ASTiDC+mpgWDGKdm3FnFLTUsLYN3iFL90lQ+PA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
@@ -1556,7 +1572,6 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -1873,6 +1888,7 @@ golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Benutzercode ungültig",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "Bezahlter Benutzer %s hat kein aktives oder ausstehendes Abonnement und die Anwendung: %s hat keine Standardpreisgestaltung",
"the application for user %s is not found": "Die Anwendung für Benutzer %s wurde nicht gefunden",
"the organization: %s is not found": "Die Organisation: %s wurde nicht gefunden"
"the organization: %s is not found": "Die Organisation: %s wurde nicht gefunden",
"The login method: login with Kerberos is not enabled for the application": "Die Anmeldemethode: Anmelden mit Kerberos ist für die Anwendung nicht aktiviert",
"Kerberos authentication failed: %s": "Kerberos-Authentifizierung fehlgeschlagen: %s"
},
"cas": {
"Service %s and %s do not match": "Service %s und %s stimmen nicht überein"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Für diesen Benutzer wurden keine Anmeldeinformationen gefunden",
"Please call WebAuthnSigninBegin first": "Bitte rufen Sie zuerst WebAuthnSigninBegin auf"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "UserCode Invalid",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "paid-user %s does not have active or pending subscription and the application: %s does not have default pricing",
"the application for user %s is not found": "the application for user %s is not found",
"the organization: %s is not found": "the organization: %s is not found"
"the organization: %s is not found": "the organization: %s is not found",
"The login method: login with Kerberos is not enabled for the application": "The login method: login with Kerberos is not enabled for the application",
"Kerberos authentication failed: %s": "Kerberos authentication failed: %s"
},
"cas": {
"Service %s and %s do not match": "Service %s and %s do not match"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Found no credentials for this user",
"Please call WebAuthnSigninBegin first": "Please call WebAuthnSigninBegin first"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Código de usuario inválido",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "El usuario de pago %s no tiene una suscripción activa o pendiente y la aplicación %s no tiene precios predeterminados",
"the application for user %s is not found": "no se encontró la aplicación para el usuario %s",
"the organization: %s is not found": "no se encontró la organización: %s"
"the organization: %s is not found": "no se encontró la organización: %s",
"The login method: login with Kerberos is not enabled for the application": "El método de inicio de sesión: iniciar sesión con Kerberos no está habilitado para la aplicación",
"Kerberos authentication failed: %s": "La autenticación Kerberos falló: %s"
},
"cas": {
"Service %s and %s do not match": "Los servicios %s y %s no coinciden"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "No se encontraron credenciales para este usuario",
"Please call WebAuthnSigninBegin first": "Por favor, llama primero a WebAuthnSigninBegin"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Code utilisateur invalide",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "L'utilisateur payant %s n'a pas d'abonnement actif ou en attente et l'application %s n'a pas de tarification par défaut",
"the application for user %s is not found": "L'application pour l'utilisateur %s est introuvable",
"the organization: %s is not found": "L'organisation : %s est introuvable"
"the organization: %s is not found": "L'organisation : %s est introuvable",
"The login method: login with Kerberos is not enabled for the application": "La méthode de connexion : connexion avec Kerberos n'est pas activée pour l'application",
"Kerberos authentication failed: %s": "Échec de l'authentification Kerberos: %s"
},
"cas": {
"Service %s and %s do not match": "Les services %s et %s ne correspondent pas"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Aucune information d'identification trouvée pour cet utilisateur",
"Please call WebAuthnSigninBegin first": "Veuillez d'abord appeler WebAuthnSigninBegin"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "ユーザーコードが無効です",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "有料ユーザー「%s」には有効または保留中のサブスクリプションがなく、アプリケーション「%s」にはデフォルトの価格設定がありません",
"the application for user %s is not found": "ユーザー「%s」のアプリケーションが見つかりません",
"the organization: %s is not found": "組織「%s」が見つかりません"
"the organization: %s is not found": "組織「%s」が見つかりません",
"The login method: login with Kerberos is not enabled for the application": "ログイン方法: Kerberosでのログインはこのアプリケーションで有効になっていません",
"Kerberos authentication failed: %s": "Kerberos認証に失敗しました: %s"
},
"cas": {
"Service %s and %s do not match": "サービス%sと%sは一致しません"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "このユーザーの認証情報が見つかりませんでした",
"Please call WebAuthnSigninBegin first": "最初にWebAuthnSigninBeginを呼び出してください"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Nieprawidłowy kod użytkownika",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "płatny użytkownik %s nie ma aktywnej lub oczekującej subskrypcji, a aplikacja: %s nie ma domyślnego cennika",
"the application for user %s is not found": "aplikacja dla użytkownika %s nie została znaleziona",
"the organization: %s is not found": "organizacja: %s nie została znaleziona"
"the organization: %s is not found": "organizacja: %s nie została znaleziona",
"The login method: login with Kerberos is not enabled for the application": "Metoda logowania: logowanie przez Kerberos nie jest włączone dla aplikacji",
"Kerberos authentication failed: %s": "Uwierzytelnianie Kerberos nie powiodło się: %s"
},
"cas": {
"Service %s and %s do not match": "Usługa %s i %s nie pasują do siebie"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Nie znaleziono danych uwierzytelniających dla tego użytkownika",
"Please call WebAuthnSigninBegin first": "Najpierw wywołaj WebAuthnSigninBegin"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Código de usuário inválido",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "O usuário pago %s não possui assinatura ativa ou pendente e o aplicativo: %s não possui preço padrão",
"the application for user %s is not found": "o aplicativo para o usuário %s não foi encontrado",
"the organization: %s is not found": "a organização: %s não foi encontrada"
"the organization: %s is not found": "a organização: %s não foi encontrada",
"The login method: login with Kerberos is not enabled for the application": "O método de login: login com Kerberos não está habilitado para a aplicação",
"Kerberos authentication failed: %s": "A autenticação Kerberos falhou: %s"
},
"cas": {
"Service %s and %s do not match": "O serviço %s e %s não correspondem"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Nenhuma credencial encontrada para este usuário",
"Please call WebAuthnSigninBegin first": "Por favor, inicie com WebAuthnSigninBegin primeiro"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Kullanıcı Kodu Geçersiz",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "Ücretli kullanıcı %s aktif veya bekleyen bir aboneliğe sahip değil ve uygulama: %s varsayılan fiyatlandırmaya sahip değil",
"the application for user %s is not found": "%s kullanıcısı için uygulama bulunamadı",
"the organization: %s is not found": "Organizasyon: %s bulunamadı"
"the organization: %s is not found": "Organizasyon: %s bulunamadı",
"The login method: login with Kerberos is not enabled for the application": "Giriş yöntemi: Kerberos ile giriş bu uygulama için etkin değil",
"Kerberos authentication failed: %s": "Kerberos kimlik doğrulaması başarısız oldu: %s"
},
"cas": {
"Service %s and %s do not match": "Servis %s ve %s eşleşmiyor"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Bu kullanıcı için kimlik bilgisi bulunamadı",
"Please call WebAuthnSigninBegin first": "Lütfen önce WebAuthnSigninBegin çağırın"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "UserCode недійсний",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "Користувач %s не має активної або очікуваної підписки, а додаток: %s не має типової ціни",
"the application for user %s is not found": "Додаток для користувача %s не знайдено",
"the organization: %s is not found": "Організація: %s не знайдена"
"the organization: %s is not found": "Організація: %s не знайдена",
"The login method: login with Kerberos is not enabled for the application": "Метод входу: вхід за допомогою Kerberos не увімкнено для цього застосунку",
"Kerberos authentication failed: %s": "Помилка автентифікації Kerberos: %s"
},
"cas": {
"Service %s and %s do not match": "Сервіс %s і %s не збігаються"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Облікові дані для цього користувача не знайдено",
"Please call WebAuthnSigninBegin first": "Спочатку викличте WebAuthnSigninBegin"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "Mã người dùng không hợp lệ",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "người dùng trả phí %s không có đăng ký đang hoạt động hoặc đang chờ và ứng dụng: %s không có giá mặc định",
"the application for user %s is not found": "không tìm thấy ứng dụng cho người dùng %s",
"the organization: %s is not found": "không tìm thấy tổ chức: %s"
"the organization: %s is not found": "không tìm thấy tổ chức: %s",
"The login method: login with Kerberos is not enabled for the application": "Phương thức đăng nhập: đăng nhập bằng Kerberos không được bật cho ứng dụng này",
"Kerberos authentication failed: %s": "Xác thực Kerberos thất bại: %s"
},
"cas": {
"Service %s and %s do not match": "Dịch sang tiếng Việt: Dịch vụ %s và %s không khớp"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "Không tìm thấy thông tin xác thực cho người dùng này",
"Please call WebAuthnSigninBegin first": "Vui lòng gọi WebAuthnSigninBegin trước"
}
}
}

View File

@@ -37,7 +37,9 @@
"UserCode Invalid": "用户代码无效",
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "付费用户 %s 没有激活或待处理的订阅,且应用 %s 没有默认定价",
"the application for user %s is not found": "未找到用户 %s 的应用程序",
"the organization: %s is not found": "组织: %s 不存在"
"the organization: %s is not found": "组织: %s 不存在",
"The login method: login with Kerberos is not enabled for the application": "应用程序未启用 Kerberos 登录方式",
"Kerberos authentication failed: %s": "Kerberos 认证失败:%s"
},
"cas": {
"Service %s and %s do not match": "服务%s与%s不匹配"
@@ -227,4 +229,4 @@
"Found no credentials for this user": "无法找到此用户的证书",
"Please call WebAuthnSigninBegin first": "请先调用WebAuthnSigninBegin函数"
}
}
}

View File

@@ -943,6 +943,17 @@ func (application *Application) IsFaceIdEnabled() bool {
return false
}
func (application *Application) IsKerberosEnabled() bool {
if len(application.SigninMethods) > 0 {
for _, signinMethod := range application.SigninMethods {
if signinMethod.Name == "Kerberos" {
return true
}
}
}
return false
}
func IsOriginAllowed(origin string) (bool, error) {
applications, err := GetApplications("")
if err != nil {

93
object/kerberos.go Normal file
View File

@@ -0,0 +1,93 @@
// Copyright 2025 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 object
import (
"encoding/base64"
"fmt"
"strings"
"github.com/jcmturner/gokrb5/v8/credentials"
"github.com/jcmturner/gokrb5/v8/gssapi"
"github.com/jcmturner/gokrb5/v8/keytab"
"github.com/jcmturner/gokrb5/v8/service"
"github.com/jcmturner/gokrb5/v8/spnego"
)
// ctxKeyKerberosCreds is the context key used by gokrb5 to store credentials.
// This matches the unexported constant in the spnego package.
const ctxKeyKerberosCreds = "github.com/jcmturner/gokrb5/v8/ctxCredentials"
// CheckKerberosToken validates a SPNEGO/Kerberos token and returns the authenticated username.
// The token is the base64-encoded value from the "Authorization: Negotiate <token>" header.
// Returns the username (without realm) on success, or an error on failure.
func CheckKerberosToken(org *Organization, tokenBase64 string) (string, error) {
if org.KerberosKeytab == "" {
return "", fmt.Errorf("Kerberos keytab is not configured for organization: %s", org.Name)
}
// Decode the base64 keytab
keytabBytes, err := base64.StdEncoding.DecodeString(org.KerberosKeytab)
if err != nil {
return "", fmt.Errorf("failed to decode Kerberos keytab: %v", err)
}
// Load the keytab
kt := keytab.New()
if err = kt.Unmarshal(keytabBytes); err != nil {
return "", fmt.Errorf("failed to parse Kerberos keytab: %v", err)
}
// Decode the SPNEGO token from base64
tokenBytes, err := base64.StdEncoding.DecodeString(tokenBase64)
if err != nil {
return "", fmt.Errorf("failed to decode Kerberos token: %v", err)
}
// Create SPNEGO service using the keytab
settings := []func(*service.Settings){}
if org.KerberosServiceName != "" {
settings = append(settings, service.SName(org.KerberosServiceName))
}
s := spnego.SPNEGOService(kt, settings...)
// Unmarshal the SPNEGO token
var st spnego.SPNEGOToken
if err = st.Unmarshal(tokenBytes); err != nil {
return "", fmt.Errorf("failed to parse SPNEGO token: %v", err)
}
// Validate the token
authed, ctx, status := s.AcceptSecContext(&st)
if status.Code != gssapi.StatusComplete && status.Code != gssapi.StatusContinueNeeded {
return "", fmt.Errorf("Kerberos authentication failed: %s", status.Message)
}
if !authed {
return "", fmt.Errorf("Kerberos authentication rejected")
}
// Extract credentials from context using the gokrb5 internal key
creds, ok := ctx.Value(ctxKeyKerberosCreds).(*credentials.Credentials)
if !ok || creds == nil {
return "", fmt.Errorf("failed to extract credentials from Kerberos context")
}
username := creds.UserName()
// Strip realm suffix if present (e.g., "user@REALM.COM" → "user")
if idx := strings.Index(username, "@"); idx != -1 {
username = username[:idx]
}
return username, nil
}

View File

@@ -98,6 +98,11 @@ type Organization struct {
UserBalance float64 `json:"userBalance"`
BalanceCredit float64 `json:"balanceCredit"`
BalanceCurrency string `xorm:"varchar(100)" json:"balanceCurrency"`
KerberosRealm string `xorm:"varchar(100)" json:"kerberosRealm"`
KerberosKdcHost string `xorm:"varchar(100)" json:"kerberosKdcHost"`
KerberosKeytab string `xorm:"mediumtext" json:"kerberosKeytab"`
KerberosServiceName string `xorm:"varchar(200)" json:"kerberosServiceName"`
}
func GetOrganizationCount(owner, name, field, value string) (int64, error) {

View File

@@ -64,6 +64,7 @@ func InitAPI() {
web.Router("/api/get-captcha-status", &controllers.ApiController{}, "GET:GetCaptchaStatus")
web.Router("/api/callback", &controllers.ApiController{}, "POST:Callback")
web.Router("/api/device-auth", &controllers.ApiController{}, "POST:DeviceAuth")
web.Router("/api/kerberos-login", &controllers.ApiController{}, "GET:GetKerberosLogin")
web.Router("/api/get-organizations", &controllers.ApiController{}, "GET:GetOrganizations")
web.Router("/api/get-organization", &controllers.ApiController{}, "GET:GetOrganization")

View File

@@ -1673,7 +1673,7 @@ class ApplicationEditPage extends React.Component {
submitApplicationEdit(exitAfterSave) {
const application = Setting.deepCopy(this.state.application);
application.providers = application.providers?.filter(provider => this.state.providers.map(provider => provider.name).includes(provider.name));
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP", "Face ID", "WeChat"].includes(signinMethod.name));
application.signinMethods = application.signinMethods?.filter(signinMethod => ["Password", "Verification code", "WebAuthn", "LDAP", "Kerberos", "Face ID", "WeChat"].includes(signinMethod.name));
const customScopeValidation = this.validateCustomScopes(application.customScopes);
application.customScopes = customScopeValidation.scopes;
if (!customScopeValidation.ok) {

View File

@@ -762,6 +762,46 @@ class OrganizationEditPage extends React.Component {
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Kerberos realm"), i18next.t("organization:Kerberos realm - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.kerberosRealm} placeholder={"e.g. EXAMPLE.COM"} onChange={e => {
this.updateOrganizationField("kerberosRealm", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Kerberos KDC host"), i18next.t("organization:Kerberos KDC host - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.kerberosKdcHost} placeholder={"e.g. kdc.example.com"} onChange={e => {
this.updateOrganizationField("kerberosKdcHost", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Kerberos service name"), i18next.t("organization:Kerberos service name - Tooltip"))} :
</Col>
<Col span={22} >
<Input value={this.state.organization.kerberosServiceName} placeholder={"e.g. HTTP/casdoor.example.com"} onChange={e => {
this.updateOrganizationField("kerberosServiceName", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("organization:Kerberos keytab"), i18next.t("organization:Kerberos keytab - Tooltip"))} :
</Col>
<Col span={22} >
<Input.TextArea rows={4} value={this.state.organization.kerberosKeytab} placeholder={i18next.t("organization:Kerberos keytab placeholder")} onChange={e => {
this.updateOrganizationField("kerberosKeytab", e.target.value);
}} />
</Col>
</Row>
</Card>
);
}

View File

@@ -1476,6 +1476,10 @@ export function isLdapEnabled(application) {
return isSigninMethodEnabled(application, "LDAP");
}
export function isKerberosEnabled(application) {
return isSigninMethodEnabled(application, "Kerberos");
}
export function isFaceIdEnabled(application) {
return isSigninMethodEnabled(application, "Face ID");
}

View File

@@ -131,6 +131,18 @@ export function getSamlLogin(providerId, relayState) {
}).then(res => res.json());
}
export function kerberosLogin(values, oAuthParams) {
const oAuthQuery = oAuthParamsToQuery(oAuthParams);
const separator = oAuthQuery ? "&" : "?";
return fetch(`${authConfig.serverUrl}/api/kerberos-login${oAuthQuery}${separator}application=${encodeURIComponent(values["application"])}`, {
method: "GET",
credentials: "include",
headers: {
"Accept-Language": Setting.getAcceptLanguage(),
},
}).then(res => res.json());
}
export function loginWithSaml(values, param) {
return fetch(`${authConfig.serverUrl}/api/login${param}`, {
method: "POST",

View File

@@ -258,6 +258,7 @@ class LoginPage extends React.Component {
case "WebAuthn": return "webAuthn";
case "LDAP": return "ldap";
case "Face ID": return "faceId";
case "Kerberos": return "kerberos";
}
}
@@ -273,6 +274,8 @@ class LoginPage extends React.Component {
return "WebAuthn";
} else if (this.state.loginMethod === "ldap") {
return "LDAP";
} else if (this.state.loginMethod === "kerberos") {
return "Kerberos";
} else if (this.state.loginMethod === "faceId") {
return "Face ID";
} else {
@@ -422,6 +425,10 @@ class LoginPage extends React.Component {
this.signInWithWebAuthn(username, values);
return;
}
if (this.state.loginMethod === "kerberos") {
this.signInWithKerberos(values);
return;
}
if (this.state.loginMethod === "faceId") {
let username = this.state.username;
if (username === null || username === "") {
@@ -711,6 +718,10 @@ class LoginPage extends React.Component {
return (<WeChatLoginPanel application={application} loginMethod={this.state.loginMethod} />);
}
if (this.state.loginMethod === "kerberos") {
return null;
}
if (this.state.loginMethod === "verificationCodePhone") {
return <Form.Item className="signin-phone" required={true}>
<Input.Group compact>
@@ -890,7 +901,8 @@ class LoginPage extends React.Component {
{
this.state.loginMethod === "webAuthn" ? i18next.t("login:Sign in with WebAuthn") :
this.state.loginMethod === "faceId" ? i18next.t("login:Sign in with Face ID") :
signinItem.label ? signinItem.label : i18next.t("login:Sign In")
this.state.loginMethod === "kerberos" ? i18next.t("login:Sign in with Kerberos") :
signinItem.label ? signinItem.label : i18next.t("login:Sign In")
}
</Button>
{
@@ -924,7 +936,7 @@ class LoginPage extends React.Component {
</Form.Item>
);
} else if (signinItem.name === "Providers") {
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application);
const showForm = Setting.isPasswordEnabled(application) || Setting.isCodeSigninEnabled(application) || Setting.isWebAuthnEnabled(application) || Setting.isLdapEnabled(application) || Setting.isKerberosEnabled(application);
if (signinItem.rule === "None" || signinItem.rule === "") {
signinItem.rule = showForm ? "small" : "big";
}
@@ -1316,6 +1328,37 @@ class LoginPage extends React.Component {
});
}
signInWithKerberos(values) {
const oAuthParams = Util.getOAuthGetParameters();
this.populateOauthValues(values);
AuthBackend.kerberosLogin(values, oAuthParams)
.then((res) => {
if (res.status === "ok") {
const responseType = values["type"];
if (responseType === "login") {
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
this.props.onLoginSuccess();
} else if (responseType === "code") {
this.postCodeLoginAction(res);
} else if (responseType === "token" || responseType === "id_token") {
const accessToken = res.data;
Setting.goToLink(`${oAuthParams.redirectUri}#${responseType}=${accessToken}&state=${oAuthParams.state}&token_type=bearer`);
} else {
Setting.showMessage("success", i18next.t("application:Logged in successfully"));
Setting.goToLink("/");
}
} else {
Setting.showMessage("error", `${i18next.t("application:Failed to sign in")}: ${res.msg}`);
}
})
.catch(error => {
Setting.showMessage("error", `${i18next.t("general:Failed to connect to server")}${error}`);
})
.finally(() => {
this.setState({loginLoading: false});
});
}
hasVerificationCodeSigninItem(application) {
const targetApp = application || this.getApplicationObj();
if (!targetApp || !targetApp.signinItems) {
@@ -1417,6 +1460,7 @@ class LoginPage extends React.Component {
[generateItemKey("Verification code", "Phone only"), {label: i18next.t("login:Verification code"), key: "verificationCodePhone"}],
[generateItemKey("WebAuthn", "None"), {label: i18next.t("login:WebAuthn"), key: "webAuthn"}],
[generateItemKey("LDAP", "None"), {label: i18next.t("login:LDAP"), key: "ldap"}],
[generateItemKey("Kerberos", "None"), {label: i18next.t("login:Kerberos"), key: "kerberos"}],
[generateItemKey("Face ID", "None"), {label: i18next.t("login:Face ID"), key: "faceId"}],
[generateItemKey("WeChat", "Tab"), {label: i18next.t("login:WeChat"), key: "wechat"}],
[generateItemKey("WeChat", "None"), {label: i18next.t("login:WeChat"), key: "wechat"}],

View File

@@ -570,7 +570,7 @@
"Physical": "Physisch",
"Show all": "Alle anzeigen",
"Virtual": "Virtuell",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Sie müssen zuerst alle Untergruppen löschen. Sie können die Untergruppen im linken Gruppenbaum unter [Organisationen] -\u003e [Gruppen] anzeigen."
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Sie müssen zuerst alle Untergruppen löschen. Sie können die Untergruppen im linken Gruppenbaum unter [Organisationen] -> [Gruppen] anzeigen."
},
"home": {
"New users past 30 days": "Neue Benutzer der letzten 30 Tage",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "Melde dich jetzt an",
"username, Email or phone": "Benutzername, E-Mail oder Telefon"
"username, Email or phone": "Benutzername, E-Mail oder Telefon",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Mit Windows-Authentifizierung anmelden"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Jedes Mal, wenn Sie sich anmelden, benötigen Sie Ihr Passwort und einen Authentifizierungscode.",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Nur in der Organisation auslösen, zu der der Webhook gehört",
"Value": "Wert"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Physical",
"Show all": "Show all",
"Virtual": "Virtual",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page"
},
"home": {
"New users past 30 days": "New users past 30 days",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "sign up now",
"username, Email or phone": "username, Email or phone"
"username, Email or phone": "username, Email or phone",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Sign in with Windows Authentication"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Each time you sign in to your Account, you'll need your password and a authentication code",
@@ -795,7 +797,16 @@
"Website URL": "Website URL",
"Website URL - Tooltip": "The homepage URL of the organization. This field is not used in Casdoor",
"Widget items": "Widget items",
"Widget items - Tooltip": "Items displayed in the widget"
"Widget items - Tooltip": "Items displayed in the widget",
"Kerberos realm": "Kerberos Realm",
"Kerberos realm - Tooltip": "The Kerberos realm (Active Directory domain name), e.g. EXAMPLE.COM",
"Kerberos KDC host": "KDC Host",
"Kerberos KDC host - Tooltip": "The Kerberos Key Distribution Center (KDC) hostname. Leave empty to use DNS discovery.",
"Kerberos service name": "Service Principal Name",
"Kerberos service name - Tooltip": "The Kerberos Service Principal Name (SPN) for this Casdoor instance, e.g. HTTP/casdoor.example.com",
"Kerberos keytab": "Keytab (Base64)",
"Kerberos keytab - Tooltip": "The service keytab file encoded in Base64, used to validate Kerberos tokens",
"Kerberos keytab placeholder": "Paste the Base64-encoded keytab file content here"
},
"payment": {
"Confirm your invoice information": "Confirm your invoice information",
@@ -1504,4 +1515,4 @@
"Single org only - Tooltip": "Triggered only in the organization that the webhook belongs to",
"Value": "Value"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Físico",
"Show all": "Mostrar todos",
"Virtual": "Virtual",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Necesitas eliminar todos los subgrupos primero. Puedes ver los subgrupos en el árbol de grupos a la izquierda en la página [Organizaciones] -\u003e [Grupos]"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Necesitas eliminar todos los subgrupos primero. Puedes ver los subgrupos en el árbol de grupos a la izquierda en la página [Organizaciones] -> [Grupos]"
},
"home": {
"New users past 30 days": "Nuevos usuarios en los últimos 30 días",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "Regístrate ahora",
"username, Email or phone": "Nombre de usuario, correo electrónico o teléfono"
"username, Email or phone": "Nombre de usuario, correo electrónico o teléfono",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Iniciar sesión con autenticación de Windows"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Cada vez que inicies sesión en tu cuenta, necesitarás tu contraseña y un código de autenticación",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Activado solo en la organización a la que pertenece el webhook",
"Value": "Valor"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Physique",
"Show all": "Afficher tout",
"Virtual": "Virtuel",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Vous devez d'abord supprimer tous les sous-groupes. Vous pouvez voir les sous-groupes dans l'arborescence des groupes à gauche de la page [Organisations] -\u003e [Groupes]"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Vous devez d'abord supprimer tous les sous-groupes. Vous pouvez voir les sous-groupes dans l'arborescence des groupes à gauche de la page [Organisations] -> [Groupes]"
},
"home": {
"New users past 30 days": "Nouveaux utilisateurs ces 30 derniers jours",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "Inscrivez-vous maintenant",
"username, Email or phone": "identifiant, adresse e-mail ou téléphone"
"username, Email or phone": "identifiant, adresse e-mail ou téléphone",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Se connecter avec l'authentification Windows"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Chaque fois que vous vous connectez à votre compte, vous aurez besoin de votre mot de passe et d'un code d'authentification",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Déclenché uniquement dans l'organisation à laquelle appartient le webhook",
"Value": "Valeur"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "物理",
"Show all": "すべて表示",
"Virtual": "仮想",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "最初にすべてのサブグループを削除する必要があります。[組織] -\u003e [グループ]ページの左側のグループツリーでサブグループを確認できます"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "最初にすべてのサブグループを削除する必要があります。[組織] -> [グループ]ページの左側のグループツリーでサブグループを確認できます"
},
"home": {
"New users past 30 days": "過去30日間の新規ユーザー",
@@ -686,7 +686,9 @@
"WeChat": "微信",
"WebAuthn": "ウェブオーセン",
"sign up now": "今すぐサインアップ",
"username, Email or phone": "ユーザー名、メールアドレス、または電話番号"
"username, Email or phone": "ユーザー名、メールアドレス、または電話番号",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Windows認証でサインイン"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "アカウントにサインインするたびに、パスワードと認証コードが必要です",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Webhookが属する組織でのみトリガーされます",
"Value": "値"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Fizyczna",
"Show all": "Pokaż wszystko",
"Virtual": "Wirtualna",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Musisz najpierw usunąć wszystkie podgrupy. Możesz przeglądać podgrupy w lewym drzewie grup na stronie [Organizacje] -\u003e [Grupy]"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Musisz najpierw usunąć wszystkie podgrupy. Możesz przeglądać podgrupy w lewym drzewie grup na stronie [Organizacje] -> [Grupy]"
},
"home": {
"New users past 30 days": "Nowi użytkownicy w ciągu ostatnich 30 dni",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "zarejestruj się teraz",
"username, Email or phone": "nazwa użytkownika, e-mail lub telefon"
"username, Email or phone": "nazwa użytkownika, e-mail lub telefon",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Zaloguj się przy użyciu uwierzytelniania Windows"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Za każdym razem, gdy logujesz się na swoje konto, potrzebujesz hasła i kodu uwierzytelniającego",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Wyzwalane tylko w organizacji, do której należy webhook",
"Value": "Wartość"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Físico",
"Show all": "Mostrar todos",
"Virtual": "Virtual",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Você precisa excluir todos os subgrupos primeiro. Você pode visualizar os subgrupos na árvore de grupos à esquerda na página [Organizações] -\u003e [Grupos]"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Você precisa excluir todos os subgrupos primeiro. Você pode visualizar os subgrupos na árvore de grupos à esquerda na página [Organizações] -> [Grupos]"
},
"home": {
"New users past 30 days": "Novos usuários nos últimos 30 dias",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "Inscreva-se agora",
"username, Email or phone": "Nome de usuário, email ou telefone"
"username, Email or phone": "Nome de usuário, email ou telefone",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Entrar com autenticação do Windows"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Cada vez que você entrar na sua conta, precisará da sua senha e de um código de autenticação",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Acionado apenas na organização a qual o webhook pertence",
"Value": "Valor"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Fiziksel",
"Show all": "Tümünü göster",
"Virtual": "Sanal",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Önce tüm alt grupları silmeniz gerekir. Alt grupları [Organizasyonlar] -\u003e [Gruplar] sayfasının sol grup ağacından görüntüleyebilirsiniz."
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Önce tüm alt grupları silmeniz gerekir. Alt grupları [Organizasyonlar] -> [Gruplar] sayfasının sol grup ağacından görüntüleyebilirsiniz."
},
"home": {
"New users past 30 days": "Son 30 gündeki yeni kullanıcılar",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "hemen kaydolun",
"username, Email or phone": "kullanıcı adınız, Eposta adresiniz ve telefon numaranız"
"username, Email or phone": "kullanıcı adınız, Eposta adresiniz ve telefon numaranız",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Windows Kimlik Doğrulaması ile giriş yap"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Hesabınıza her oturum açtığınızda şifreniz ve bir doğrulama kodu gerekecek",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Webhook'un ait olduğu organizasyonda yalnızca tetiklenir",
"Value": "Değer"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "фізичний",
"Show all": "Покажи все",
"Virtual": "Віртуальний",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Спочатку потрібно видалити всі підгрупи. Підгрупи можна переглянути у лівому дереві груп на сторінці [Організації] -\u003e [Групи]"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Спочатку потрібно видалити всі підгрупи. Підгрупи можна переглянути у лівому дереві груп на сторінці [Організації] -> [Групи]"
},
"home": {
"New users past 30 days": "Нові користувачі за останні 30 днів",
@@ -686,7 +686,9 @@
"WeChat": "Вейчат",
"WebAuthn": "WebAuthn",
"sign up now": "Зареєструйся зараз",
"username, Email or phone": "ім'я користувача, електронну пошту або телефон"
"username, Email or phone": "ім'я користувача, електронну пошту або телефон",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Увійти через автентифікацію Windows"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Кожного разу, коли ви входите в обліковий запис, вам знадобляться пароль і код автентифікації",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Активується лише в організації, якій належить вебхук",
"Value": "Значення"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "Vật lý",
"Show all": "Hiển thị tất cả",
"Virtual": "Ảo",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "Bạn cần xóa tất cả nhóm con trước. Bạn có thể xem các nhóm con trong cây nhóm bên trái của trang [Tổ chức] -\u003e [Nhóm]"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "Bạn cần xóa tất cả nhóm con trước. Bạn có thể xem các nhóm con trong cây nhóm bên trái của trang [Tổ chức] -> [Nhóm]"
},
"home": {
"New users past 30 days": "Người dùng mới trong 30 ngày qua",
@@ -686,7 +686,9 @@
"WeChat": "WeChat",
"WebAuthn": "WebAuthn",
"sign up now": "Đăng ký ngay bây giờ",
"username, Email or phone": "Tên đăng nhập, Email hoặc điện thoại"
"username, Email or phone": "Tên đăng nhập, Email hoặc điện thoại",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "Đăng nhập bằng xác thực Windows"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "Mỗi lần đăng nhập vào Tài khoản của bạn, bạn sẽ cần mật khẩu và mã xác thực",
@@ -1504,4 +1506,4 @@
"Single org only - Tooltip": "Chỉ kích hoạt trong tổ chức mà webhook thuộc về",
"Value": "Giá trị"
}
}
}

View File

@@ -570,7 +570,7 @@
"Physical": "实体组",
"Show all": "显示全部",
"Virtual": "虚拟组",
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -\u003e [Groups] page": "您需要先删除所有子组。您可以在 [组织] -\u003e [群组] 页面左侧的群组树中查看子组"
"You need to delete all subgroups first. You can view the subgroups in the left group tree of the [Organizations] -> [Groups] page": "您需要先删除所有子组。您可以在 [组织] -> [群组] 页面左侧的群组树中查看子组"
},
"home": {
"New users past 30 days": "过去 30 天新增的用户",
@@ -686,7 +686,9 @@
"WeChat": "微信",
"WebAuthn": "Web身份验证",
"sign up now": "立即注册",
"username, Email or phone": "用户名、Email或手机号"
"username, Email or phone": "用户名、Email或手机号",
"Kerberos": "Kerberos",
"Sign in with Kerberos": "使用Windows集成认证登录"
},
"mfa": {
"Each time you sign in to your Account, you'll need your password and a authentication code": "每次登录帐户时,都需要密码和认证码",
@@ -795,7 +797,16 @@
"Website URL": "主页地址",
"Website URL - Tooltip": "组织的主页地址URL该字段在Casdoor平台中未被使用",
"Widget items": "功能按钮",
"Widget items - Tooltip": "小部件中显示的项目"
"Widget items - Tooltip": "小部件中显示的项目",
"Kerberos realm": "Kerberos 域",
"Kerberos realm - Tooltip": "Kerberos 域Active Directory 域名),例如 EXAMPLE.COM",
"Kerberos KDC host": "KDC 主机",
"Kerberos KDC host - Tooltip": "Kerberos 密钥分发中心KDC主机名留空则使用 DNS 自动发现",
"Kerberos service name": "服务主体名称",
"Kerberos service name - Tooltip": "此 Casdoor 实例的 Kerberos 服务主体名称SPN例如 HTTP/casdoor.example.com",
"Kerberos keytab": "KeytabBase64",
"Kerberos keytab - Tooltip": "Base64 编码的服务 keytab 文件,用于验证 Kerberos 令牌",
"Kerberos keytab placeholder": "在此处粘贴 Base64 编码的 keytab 文件内容"
},
"payment": {
"Confirm your invoice information": "确认您的发票信息",
@@ -1504,4 +1515,4 @@
"Single org only - Tooltip": "仅在Webhook所在组织触发",
"Value": "值"
}
}
}

View File

@@ -71,6 +71,7 @@ class SigninMethodTable extends React.Component {
{name: "Verification code", displayName: i18next.t("login:Verification code")},
{name: "WebAuthn", displayName: i18next.t("login:WebAuthn")},
{name: "LDAP", displayName: i18next.t("login:LDAP")},
{name: "Kerberos", displayName: i18next.t("login:Kerberos")},
{name: "Face ID", displayName: i18next.t("login:Face ID")},
{name: "WeChat", displayName: i18next.t("login:WeChat")},
];