Compare commits

..

1 Commits

Author SHA1 Message Date
mr
b84c2ef353 workin oc-auth 2025-01-17 17:24:08 +01:00
26 changed files with 1443 additions and 407 deletions

View File

@ -1,48 +1,32 @@
FROM golang:alpine AS deps FROM golang:alpine as builder
WORKDIR /app
COPY go.mod go.sum ./
RUN sed -i '/replace/d' go.mod
RUN cat go.mod
RUN go mod download
#----------------------------------------------------------------------------------------------
FROM golang:alpine AS builder
ARG HOSTNAME=http://localhost ARG HOSTNAME=http://localhost
ARG NAME=local ARG NAME=local
RUN apk add git WORKDIR /app
RUN go install github.com/beego/bee/v2@latest
WORKDIR /oc-auth
COPY --from=deps /go/pkg /go/pkg
COPY --from=deps /app/go.mod /app/go.sum ./
RUN export CGO_ENABLED=0 && \
export GOOS=linux && \
export GOARCH=amd64 && \
export BUILD_FLAGS="-ldflags='-w -s'"
COPY . . COPY . .
RUN sed -i '/replace/d' go.mod RUN apk add git
RUN bee pack
RUN mkdir -p /app/extracted && tar -zxvf oc-auth.tar.gz -C /app/extracted
RUN sed -i 's/http:\/\/127.0.0.1:8080\/swagger\/swagger.json/swagger.json/g' /app/extracted/swagger/index.html
#---------------------------------------------------------------------------------------------- RUN go get github.com/beego/bee/v2 && go install github.com/beego/bee/v2@master
FROM golang:alpine RUN timeout 15 bee run -gendoc=true -downdoc=true -runmode=dev || :
RUN sed -i 's/http:\/\/127.0.0.1:8080\/swagger\/swagger.json/swagger.json/g' swagger/index.html
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" .
RUN ls /app
FROM scratch
WORKDIR /app WORKDIR /app
COPY --from=builder /app/extracted/oc-auth /usr/bin
COPY --from=builder /app/extracted/swagger /app/swagger COPY --from=builder /app/oc-auth /usr/bin/
COPY --from=builder /app/extracted/pem /app/pem COPY --from=builder /app/swagger /app/swagger
COPY --from=builder /app/extracted/docker_auth.json /etc/oc/auth.json
COPY docker_auth.json /etc/oc/auth.json
EXPOSE 8080 EXPOSE 8080

View File

@ -1,27 +0,0 @@
.DEFAULT_GOAL := all
build: clean
bee pack
run:
bee run -gendoc=true -downdoc=true
debug:
bee run -downdebug -gendebug
clean:
rm -rf oc-auth oc-auth.tar.gz
docker:
DOCKER_BUILDKIT=1 docker build -t oc/oc-auth:0.0.1 -f Dockerfile .
docker tag oc/oc-auth:0.0.1 oc/oc-auth:latest
publish-kind:
kind load docker-image oc/oc-auth:0.0.1 --name opencloud
publish-registry:
@echo "TODO"
all: docker publish-kind publish-registry
.PHONY: build run clean docker publish-kind publish-registry

BIN
__debug_bin142225022 Executable file

Binary file not shown.

View File

@ -3,6 +3,7 @@ package conf
import "sync" import "sync"
type Config struct { type Config struct {
SourceMode string
AdminRole string AdminRole string
PublicKeyPath string PublicKeyPath string
PrivateKeyPath string PrivateKeyPath string
@ -13,12 +14,9 @@ type Config struct {
LDAPBaseDN string LDAPBaseDN string
LDAPRoleBaseDN string LDAPRoleBaseDN string
ClientSecret string ClientSecret string
OAuth2ClientSecretName string
OAuth2ClientSecretNamespace string
Auth string Auth string
AuthConnectPublicHost string
AuthConnectorHost string AuthConnectorHost string
AuthConnectorPort int AuthConnectorPort int
AuthConnectorAdminPort int AuthConnectorAdminPort int

View File

@ -19,7 +19,8 @@ type GroupController struct {
func (o *GroupController) Post() { func (o *GroupController) Post() {
// store and return Id or post with UUID // store and return Id or post with UUID
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
group, code, err := infrastructure.GetPermissionConnector().CreateGroup(id) clientID := ExtractClient(*o.Ctx.Request)
group, code, err := infrastructure.GetPermissionConnector(clientID).CreateGroup(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -44,7 +45,8 @@ func (o *GroupController) Post() {
// @router /user/:id [get] // @router /user/:id [get]
func (o *GroupController) GetByUser() { func (o *GroupController) GetByUser() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
group, err := infrastructure.GetPermissionConnector().GetGroupByUser(id) clientID := ExtractClient(*o.Ctx.Request)
group, err := infrastructure.GetPermissionConnector(clientID).GetGroupByUser(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -67,7 +69,8 @@ func (o *GroupController) GetByUser() {
// @Success 200 {group} string // @Success 200 {group} string
// @router / [get] // @router / [get]
func (o *GroupController) GetAll() { func (o *GroupController) GetAll() {
group, err := infrastructure.GetPermissionConnector().GetGroup("") clientID := ExtractClient(*o.Ctx.Request)
group, err := infrastructure.GetPermissionConnector(clientID).GetGroup("")
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -92,7 +95,8 @@ func (o *GroupController) GetAll() {
// @router /:id [get] // @router /:id [get]
func (o *GroupController) Get() { func (o *GroupController) Get() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
group, err := infrastructure.GetPermissionConnector().GetGroup(id) clientID := ExtractClient(*o.Ctx.Request)
group, err := infrastructure.GetPermissionConnector(clientID).GetGroup(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -117,7 +121,8 @@ func (o *GroupController) Get() {
// @router /:id [delete] // @router /:id [delete]
func (o *GroupController) Delete() { func (o *GroupController) Delete() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
group, code, err := infrastructure.GetPermissionConnector().DeleteGroup(id) clientID := ExtractClient(*o.Ctx.Request)
group, code, err := infrastructure.GetPermissionConnector(clientID).DeleteGroup(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -140,7 +145,8 @@ func (o *GroupController) Delete() {
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
// @router /clear [delete] // @router /clear [delete]
func (o *GroupController) Clear() { func (o *GroupController) Clear() {
group, code, err := infrastructure.GetPermissionConnector().DeleteGroup("") clientID := ExtractClient(*o.Ctx.Request)
group, code, err := infrastructure.GetPermissionConnector(clientID).DeleteGroup("")
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -167,7 +173,8 @@ func (o *GroupController) Clear() {
func (o *GroupController) Bind() { func (o *GroupController) Bind() {
user_id := o.Ctx.Input.Param(":user_id") user_id := o.Ctx.Input.Param(":user_id")
group_id := o.Ctx.Input.Param(":group_id") group_id := o.Ctx.Input.Param(":group_id")
group, code, err := infrastructure.GetPermissionConnector().BindGroup(user_id, group_id) clientID := ExtractClient(*o.Ctx.Request)
group, code, err := infrastructure.GetPermissionConnector(clientID).BindGroup(user_id, group_id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -187,14 +194,15 @@ func (o *GroupController) Bind() {
// @Title UnBind // @Title UnBind
// @Description unbind the group to user // @Description unbind the group to user
// @Param group_id path string true "The group_id you want to unbind" // @Param user_id path string true "The group_id you want to unbind"
// @Param group_id path string true "The user_id you want to unbind" // @Param group_id path string true "The user_id you want to unbind"
// @Success 200 {string} bind success! // @Success 200 {string} bind success!
// @router /:user_id/:group_id [delete] // @router /:user_id/:group_id [delete]
func (o *GroupController) UnBind() { func (o *GroupController) UnBind() {
user_id := o.Ctx.Input.Param(":user_id") user_id := o.Ctx.Input.Param(":user_id")
group_id := o.Ctx.Input.Param(":group_id") group_id := o.Ctx.Input.Param(":group_id")
group, code, err := infrastructure.GetPermissionConnector().UnBindGroup(user_id, group_id) clientID := ExtractClient(*o.Ctx.Request)
group, code, err := infrastructure.GetPermissionConnector(clientID).UnBindGroup(user_id, group_id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,

View File

@ -1,9 +1,11 @@
package controllers package controllers
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"oc-auth/conf"
"oc-auth/infrastructure" "oc-auth/infrastructure"
auth_connectors "oc-auth/infrastructure/auth_connector" auth_connectors "oc-auth/infrastructure/auth_connector"
"regexp" "regexp"
@ -22,10 +24,12 @@ type OAuthController struct {
// @Title Logout // @Title Logout
// @Description unauthenticate user // @Description unauthenticate user
// @Param Authorization header string false "auth token" // @Param Authorization header string false "auth token"
// @Param client_id query string true "the client_id you want to get"
// @Success 200 {string} // @Success 200 {string}
// @router /ldap/logout [delete] // @router /logout [delete]
func (o *OAuthController) LogOutLDAP() { func (o *OAuthController) LogOut() {
// authorize user // authorize user
clientID := o.Ctx.Input.Query("client_id")
reqToken := o.Ctx.Request.Header.Get("Authorization") reqToken := o.Ctx.Request.Header.Get("Authorization")
splitToken := strings.Split(reqToken, "Bearer ") splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) < 2 { if len(splitToken) < 2 {
@ -36,7 +40,7 @@ func (o *OAuthController) LogOutLDAP() {
var res auth_connectors.Token var res auth_connectors.Token
json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &res) json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &res)
token, err := infrastructure.GetAuthConnector().Logout(reqToken) token, err := infrastructure.GetAuthConnector().Logout(clientID, reqToken)
if err != nil || token == nil { if err != nil || token == nil {
o.Data["json"] = err o.Data["json"] = err
} else { } else {
@ -48,25 +52,33 @@ func (o *OAuthController) LogOutLDAP() {
// @Title Login // @Title Login
// @Description authenticate user // @Description authenticate user
// @Param body body models.workflow true "The workflow content" // @Param body body models.workflow true "The workflow content"
// @Param client_id query string true "the client_id you want to get"
// @Success 200 {string} // @Success 200 {string}
// @router /ldap/login [post] // @router /login [post]
func (o *OAuthController) LoginLDAP() { func (o *OAuthController) Login() {
// authorize user // authorize user
fmt.Println("Login", o.Ctx.Input.Query("client_id"), o.Ctx.Input.Param(":client_id"))
clientID := o.Ctx.Input.Query("client_id")
var res auth_connectors.Token var res auth_connectors.Token
json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &res) json.Unmarshal(o.Ctx.Input.CopyBody(10000000), &res)
ldap := auth_connectors.New() if conf.GetConfig().SourceMode == "ldap" {
found, err := ldap.Authenticate(o.Ctx.Request.Context(), res.Username, res.Password) ldap := auth_connectors.New()
if err != nil || !found { found, err := ldap.Authenticate(o.Ctx.Request.Context(), res.Username, res.Password)
o.Data["json"] = err fmt.Println("found", found, "err", err)
o.Ctx.ResponseWriter.WriteHeader(401) if err != nil || !found {
o.ServeJSON() o.Data["json"] = err
return o.Ctx.ResponseWriter.WriteHeader(401)
o.ServeJSON()
return
}
} }
token, err := infrastructure.GetAuthConnector().Login(res.Username, token, err := infrastructure.GetAuthConnector().Login(
clientID, res.Username,
&http.Cookie{ // open a session &http.Cookie{ // open a session
Name: "csrf_token", Name: "csrf_token",
Value: o.XSRFToken(), Value: o.XSRFToken(),
}) })
fmt.Println("token", token, "err", err)
if err != nil || token == nil { if err != nil || token == nil {
o.Data["json"] = err o.Data["json"] = err
o.Ctx.ResponseWriter.WriteHeader(401) o.Ctx.ResponseWriter.WriteHeader(401)
@ -79,13 +91,15 @@ func (o *OAuthController) LoginLDAP() {
// @Title Introspection // @Title Introspection
// @Description introspect token // @Description introspect token
// @Param body body models.Token true "The token info" // @Param body body models.Token true "The token info"
// @Param client_id query string true "the client_id you want to get"
// @Success 200 {string} // @Success 200 {string}
// @router /refresh [post] // @router /refresh [post]
func (o *OAuthController) Refresh() { func (o *OAuthController) Refresh() {
clientID := o.Ctx.Input.Query("client_id")
var token auth_connectors.Token var token auth_connectors.Token
json.Unmarshal(o.Ctx.Input.CopyBody(100000), &token) json.Unmarshal(o.Ctx.Input.CopyBody(100000), &token)
// refresh token // refresh token
newToken, err := infrastructure.GetAuthConnector().Refresh(&token) newToken, err := infrastructure.GetAuthConnector().Refresh(clientID, &token)
if err != nil || newToken == nil { if err != nil || newToken == nil {
o.Data["json"] = err o.Data["json"] = err
o.Ctx.ResponseWriter.WriteHeader(401) o.Ctx.ResponseWriter.WriteHeader(401)
@ -128,7 +142,7 @@ var whitelist = []string{
// @Param Authorization header string false "auth token" // @Param Authorization header string false "auth token"
// @Success 200 {string} // @Success 200 {string}
// @router /forward [get] // @router /forward [get]
func (o *OAuthController) InternalAuthForward() { func (o *OAuthController) InternaisDraftlAuthForward() {
fmt.Println("InternalAuthForward") fmt.Println("InternalAuthForward")
reqToken := o.Ctx.Request.Header.Get("Authorization") reqToken := o.Ctx.Request.Header.Get("Authorization")
if reqToken == "" { if reqToken == "" {
@ -149,7 +163,7 @@ func (o *OAuthController) InternalAuthForward() {
} else { } else {
reqToken = splitToken[1] reqToken = splitToken[1]
} }
origin, publicKey, external := o.extractOrigin() origin, publicKey, external := o.extractOrigin(o.Ctx.Request)
if !infrastructure.GetAuthConnector().CheckAuthForward( //reqToken != "" && if !infrastructure.GetAuthConnector().CheckAuthForward( //reqToken != "" &&
reqToken, publicKey, origin, reqToken, publicKey, origin,
o.Ctx.Request.Header.Get("X-Forwarded-Method"), o.Ctx.Request.Header.Get("X-Forwarded-Method"),
@ -161,7 +175,8 @@ func (o *OAuthController) InternalAuthForward() {
o.ServeJSON() o.ServeJSON()
} }
func (o *OAuthController) extractOrigin() (string, string, bool) { func (o *OAuthController) extractOrigin(request *http.Request) (string, string, bool) {
user, peerID, groups := oclib.ExtractTokenInfo(*request)
external := true external := true
publicKey := "" publicKey := ""
origin := o.Ctx.Request.Header.Get("X-Forwarded-Host") origin := o.Ctx.Request.Header.Get("X-Forwarded-Host")
@ -174,7 +189,7 @@ func (o *OAuthController) extractOrigin() (string, string, bool) {
if t != "" { if t != "" {
searchStr = strings.Replace(searchStr, t, "", -1) searchStr = strings.Replace(searchStr, t, "", -1)
} }
peer := oclib.Search(nil, searchStr, oclib.LibDataEnum(oclib.PEER)) peer := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), user, peerID, groups, nil).Search(nil, searchStr, false)
if peer.Code != 200 || len(peer.Data) == 0 { // TODO: add state of partnership if peer.Code != 200 || len(peer.Data) == 0 { // TODO: add state of partnership
return "", "", external return "", "", external
} }
@ -190,3 +205,29 @@ func (o *OAuthController) extractOrigin() (string, string, bool) {
} }
return origin, publicKey, external return origin, publicKey, external
} }
func ExtractClient(request http.Request) string {
reqToken := request.Header.Get("Authorization")
splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) < 2 {
reqToken = ""
} else {
reqToken = splitToken[1]
}
if reqToken != "" {
token := strings.Split(reqToken, ".")
if len(token) > 2 {
bytes, err := base64.StdEncoding.DecodeString(token[2])
if err != nil {
return ""
}
m := map[string]interface{}{}
err = json.Unmarshal(bytes, &m)
if err != nil {
return ""
}
return m["session"].(map[string]interface{})["id_token"].(map[string]interface{})["client_id"].(string)
}
}
return ""
}

View File

@ -16,7 +16,8 @@ type PermissionController struct {
// @Success 200 {permission} string // @Success 200 {permission} string
// @router / [get] // @router / [get]
func (o *PermissionController) GetAll() { func (o *PermissionController) GetAll() {
role, err := infrastructure.GetPermissionConnector().GetPermission("", "") clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetPermission("", "")
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -41,7 +42,8 @@ func (o *PermissionController) GetAll() {
// @router /role/:id [get] // @router /role/:id [get]
func (o *PermissionController) GetByRole() { func (o *PermissionController) GetByRole() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
role, err := infrastructure.GetPermissionConnector().GetPermissionByRole(id) clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetPermissionByRole(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -66,7 +68,8 @@ func (o *PermissionController) GetByRole() {
// @router /user/:id [get] // @router /user/:id [get]
func (o *PermissionController) GetByUser() { func (o *PermissionController) GetByUser() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
role, err := infrastructure.GetPermissionConnector().GetPermissionByUser(id, true) clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetPermissionByUser(id, true)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -92,7 +95,8 @@ func (o *PermissionController) GetByUser() {
func (o *PermissionController) Get() { func (o *PermissionController) Get() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
rel := o.Ctx.Input.Param(":relation") rel := o.Ctx.Input.Param(":relation")
role, err := infrastructure.GetPermissionConnector().GetPermission(id, rel) clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetPermission(id, rel)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -115,7 +119,8 @@ func (o *PermissionController) Get() {
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
// @router /clear [delete] // @router /clear [delete]
func (o *PermissionController) Clear() { func (o *PermissionController) Clear() {
role, code, err := infrastructure.GetPermissionConnector().DeletePermission("", "", true) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).DeletePermission("", "", true)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -144,7 +149,8 @@ func (o *PermissionController) Bind() {
permission_id := o.Ctx.Input.Param(":permission_id") permission_id := o.Ctx.Input.Param(":permission_id")
role_id := o.Ctx.Input.Param(":role_id") role_id := o.Ctx.Input.Param(":role_id")
rel := o.Ctx.Input.Param(":relation") rel := o.Ctx.Input.Param(":relation")
role, code, err := infrastructure.GetPermissionConnector().BindPermission(role_id, permission_id, rel) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).BindPermission(role_id, permission_id, rel)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -173,7 +179,8 @@ func (o *PermissionController) UnBind() {
permission_id := o.Ctx.Input.Param(":permission_id") permission_id := o.Ctx.Input.Param(":permission_id")
role_id := o.Ctx.Input.Param(":role_id") role_id := o.Ctx.Input.Param(":role_id")
rel := o.Ctx.Input.Param(":relation") rel := o.Ctx.Input.Param(":relation")
role, code, err := infrastructure.GetPermissionConnector().UnBindPermission(role_id, permission_id, rel) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).UnBindPermission(role_id, permission_id, rel)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,

View File

@ -19,7 +19,8 @@ type RoleController struct {
func (o *RoleController) Post() { func (o *RoleController) Post() {
// store and return Id or post with UUID // store and return Id or post with UUID
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
role, code, err := infrastructure.GetPermissionConnector().CreateRole(id) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).CreateRole(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -44,7 +45,8 @@ func (o *RoleController) Post() {
// @router /user/:id [get] // @router /user/:id [get]
func (o *RoleController) GetByUser() { func (o *RoleController) GetByUser() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
role, err := infrastructure.GetPermissionConnector().GetRoleByUser(id) clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetRoleByUser(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -67,7 +69,8 @@ func (o *RoleController) GetByUser() {
// @Success 200 {role} string // @Success 200 {role} string
// @router / [get] // @router / [get]
func (o *RoleController) GetAll() { func (o *RoleController) GetAll() {
role, err := infrastructure.GetPermissionConnector().GetRole("") clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetRole("")
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -92,7 +95,8 @@ func (o *RoleController) GetAll() {
// @router /:id [get] // @router /:id [get]
func (o *RoleController) Get() { func (o *RoleController) Get() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
role, err := infrastructure.GetPermissionConnector().GetRole(id) clientID := ExtractClient(*o.Ctx.Request)
role, err := infrastructure.GetPermissionConnector(clientID).GetRole(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -117,7 +121,8 @@ func (o *RoleController) Get() {
// @router /:id [delete] // @router /:id [delete]
func (o *RoleController) Delete() { func (o *RoleController) Delete() {
id := o.Ctx.Input.Param(":id") id := o.Ctx.Input.Param(":id")
role, code, err := infrastructure.GetPermissionConnector().DeleteRole(id) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).DeleteRole(id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -140,7 +145,8 @@ func (o *RoleController) Delete() {
// @Success 200 {string} delete success! // @Success 200 {string} delete success!
// @router /clear [delete] // @router /clear [delete]
func (o *RoleController) Clear() { func (o *RoleController) Clear() {
role, code, err := infrastructure.GetPermissionConnector().DeleteRole("") clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).DeleteRole("")
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -167,7 +173,8 @@ func (o *RoleController) Clear() {
func (o *RoleController) Bind() { func (o *RoleController) Bind() {
user_id := o.Ctx.Input.Param(":user_id") user_id := o.Ctx.Input.Param(":user_id")
role_id := o.Ctx.Input.Param(":role_id") role_id := o.Ctx.Input.Param(":role_id")
role, code, err := infrastructure.GetPermissionConnector().BindRole(user_id, role_id) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).BindRole(user_id, role_id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,
@ -194,7 +201,8 @@ func (o *RoleController) Bind() {
func (o *RoleController) UnBind() { func (o *RoleController) UnBind() {
user_id := o.Ctx.Input.Param(":user_id") user_id := o.Ctx.Input.Param(":user_id")
role_id := o.Ctx.Input.Param(":role_id") role_id := o.Ctx.Input.Param(":role_id")
role, code, err := infrastructure.GetPermissionConnector().UnBindRole(user_id, role_id) clientID := ExtractClient(*o.Ctx.Request)
role, code, err := infrastructure.GetPermissionConnector(clientID).UnBindRole(user_id, role_id)
if err != nil { if err != nil {
o.Data["json"] = map[string]interface{}{ o.Data["json"] = map[string]interface{}{
"data": nil, "data": nil,

21
docker-compose-2.yml Normal file
View File

@ -0,0 +1,21 @@
version: '3.4'
services:
oc-auth-2:
image: 'oc-auth-2:latest'
ports:
- 8095:8080
container_name: oc-auth-2
environment:
LDAP_ENDPOINTS: ldap-2:389
LDAP_BINDDN: cn=admin,dc=example,dc=com
LDAP_BINDPW: password
LDAP_BASEDN: "dc=example,dc=com"
LDAP_ROLE_BASEDN: "ou=AppRoles,dc=example,dc=com"
networks:
- catalog
volumes:
- ./pem:/etc/oc/pem
networks:
catalog:
external: true

112
go.mod
View File

@ -1,59 +1,85 @@
module oc-auth module oc-auth
go 1.23.0 go 1.22.0
toolchain go1.23.3
require ( require (
cloud.o-forge.io/core/oc-lib v0.0.0-20250108155542-0f4adeea86be cloud.o-forge.io/core/oc-lib v0.0.0-20250117152246-b85ca8674b27
github.com/beego/beego/v2 v2.3.1 github.com/beego/beego/v2 v2.3.1
github.com/nats-io/nats.go v1.37.0
github.com/ory/hydra-client-go v1.11.8
github.com/smartystreets/goconvey v1.7.2 github.com/smartystreets/goconvey v1.7.2
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
k8s.io/apimachinery v0.32.1 golang.org/x/oauth2 v0.23.0
k8s.io/client-go v0.32.1
) )
//replace cloud.o-forge.io/core/oc-lib => ../oc-lib
require ( require (
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/biter777/countries v1.7.5 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
github.com/go-logr/logr v1.4.2 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/gobuffalo/pop/v6 v6.0.8 // indirect
github.com/gofrs/uuid v4.3.0+incompatible // indirect github.com/gofrs/uuid v4.3.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect github.com/golang/glog v1.2.0 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.2 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/nats-io/nats.go v1.37.0 // indirect github.com/marcinwyszynski/geopoint v0.0.0-20140302213024-cf2a6f750c5b // indirect
github.com/mattn/goveralls v0.0.12 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/openzipkin/zipkin-go v0.4.1 // indirect
github.com/ory/go-acc v0.2.9-0.20230103102148-6b1c9a70dbbe // indirect
github.com/ory/go-convenience v0.1.0 // indirect
github.com/ory/x v0.0.575 // indirect
github.com/pelletier/go-toml/v2 v2.0.9 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect github.com/robfig/cron v1.2.0 // indirect
github.com/seatgeek/logrus-gelf-formatter v0.0.0-20210414080842-5b05eb8ff761 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/spf13/viper v1.16.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.42.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.42.0 // indirect
go.opentelemetry.io/contrib/propagators/b3 v1.17.0 // indirect
go.opentelemetry.io/contrib/propagators/jaeger v1.17.0 // indirect
go.opentelemetry.io/contrib/samplers/jaegerremote v0.11.0 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.16.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.16.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.16.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/sdk v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect go.uber.org/multierr v1.10.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/term v0.25.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
golang.org/x/time v0.7.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
gopkg.in/inf.v0 v0.9.1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
k8s.io/api v0.32.1 // indirect google.golang.org/grpc v1.63.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
) )
require ( require (
@ -65,6 +91,7 @@ require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
@ -72,7 +99,10 @@ require (
github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/i-core/rlog v1.0.0 github.com/i-core/rlog v1.0.0
github.com/jtolds/gls v4.20.0+incompatible // indirect github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/justinas/nosurf v1.1.1
github.com/kelseyhightower/envconfig v1.4.0
github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/compress v1.17.11 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
@ -81,10 +111,13 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/nats-io/nkeys v0.4.7 // indirect github.com/nats-io/nkeys v0.4.7 // indirect
github.com/nats-io/nuid v1.0.1 // indirect github.com/nats-io/nuid v1.0.1 // indirect
github.com/ory/fosite v0.47.0
github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/purnaresa/bulwark v0.0.0-20201001150757-1cec324746b2
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect github.com/rs/zerolog v1.33.0 // indirect
github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect
github.com/smartystreets/assertions v1.2.0 // indirect github.com/smartystreets/assertions v1.2.0 // indirect
@ -98,6 +131,7 @@ require (
golang.org/x/sync v0.8.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.35.1 // indirect google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

955
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -9,10 +9,10 @@ import (
type AuthConnector interface { type AuthConnector interface {
Status() tools.State Status() tools.State
Login(username string, cookies ...*http.Cookie) (*Token, error) Login(clientID string, username string, cookies ...*http.Cookie) (*Token, error)
Logout(token string, cookies ...*http.Cookie) (*Token, error) Logout(clientID string, token string, cookies ...*http.Cookie) (*Token, error)
Introspect(token string, cookie ...*http.Cookie) (bool, error) Introspect(token string, cookie ...*http.Cookie) (bool, error)
Refresh(token *Token) (*Token, error) Refresh(client_id string, token *Token) (*Token, error)
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string, external bool) bool
} }

View File

@ -1,8 +1,6 @@
package auth_connectors package auth_connectors
import ( import (
"bytes"
"context"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
@ -12,7 +10,6 @@ import (
"net/url" "net/url"
"oc-auth/conf" "oc-auth/conf"
"oc-auth/infrastructure/claims" "oc-auth/infrastructure/claims"
"os"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -21,16 +18,11 @@ import (
oclib "cloud.o-forge.io/core/oc-lib" oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/models/peer" "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
) )
type HydraConnector struct { type HydraConnector struct {
State string `json:"state"` State string `json:"state"`
Scopes string `json:"scope"` Scopes string `json:"scope"`
ClientID string `json:"client_id"`
ResponseType string `json:"response_type"` ResponseType string `json:"response_type"`
Caller *tools.HTTPCaller Caller *tools.HTTPCaller
@ -92,7 +84,7 @@ func (a HydraConnector) challenge(username string, url string, challenge string,
return &token, s[1], cookies, nil return &token, s[1], cookies, nil
} }
func (a HydraConnector) Refresh(token *Token) (*Token, error) { func (a HydraConnector) Refresh(client_id string, token *Token) (*Token, error) {
access := strings.Split(token.AccessToken, ".") access := strings.Split(token.AccessToken, ".")
if len(access) > 2 { if len(access) > 2 {
token.AccessToken = strings.Join(access[0:2], ".") token.AccessToken = strings.Join(access[0:2], ".")
@ -101,34 +93,20 @@ func (a HydraConnector) Refresh(token *Token) (*Token, error) {
if err != nil || !isValid { if err != nil || !isValid {
return nil, err return nil, err
} }
_, err = a.Logout(token.AccessToken) _, err = a.Logout(client_id, token.AccessToken)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return a.Login(token.Username) return a.Login(client_id, token.Username)
} }
func (a HydraConnector) tryLog(username string, url string, subpath string, challenge string, cookies ...*http.Cookie) (*Redirect, string, []*http.Cookie, error) { func (a HydraConnector) tryLog(username string, url string, subpath string, challenge string, cookies ...*http.Cookie) (*Redirect, string, []*http.Cookie, error) {
resp, err := a.Caller.CallRaw(http.MethodGet, url, subpath,
postBody, _ := json.Marshal(map[string]interface{}{}) map[string]interface{}{}, "application/json", true, cookies...)
responseBody := bytes.NewBuffer(postBody) if err != nil || resp.Request.Response == nil || resp.Request.Response.Header["Set-Cookie"] == nil {
req, _ := http.NewRequest(http.MethodGet, url+subpath, responseBody)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("X-Forwarded-Proto", "https")
for _, c := range cookies {
req.AddCookie(c)
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // No redirect, doesn't make sense; hydra redirect user to login page, we are not the user here due to wrong oauth flow implementation
},
}
resp, err := client.Do(req)
if err != nil || resp == nil || resp.Header["Set-Cookie"] == nil {
return nil, "", cookies, err return nil, "", cookies, err
} }
cc := resp.Header["Set-Cookie"] // retrieve oauth2 csrf token cookie cc := resp.Request.Response.Header["Set-Cookie"] // retrieve oauth2 csrf token cookie
if len(cc) > 0 { if len(cc) > 0 {
for _, c := range cc { for _, c := range cc {
first := strings.Split(c, ";") first := strings.Split(c, ";")
@ -138,10 +116,10 @@ func (a HydraConnector) tryLog(username string, url string, subpath string, chal
}) })
} }
} }
return a.challenge(username, resp.Header.Get("Location"), challenge, cookies...) return a.challenge(username, resp.Request.URL.String(), challenge, cookies...)
} }
func (a HydraConnector) getClient() string { func (a HydraConnector) getClient(clientID string) string {
resp, err := a.Caller.CallGet(a.getPath(true, false), "/clients") resp, err := a.Caller.CallGet(a.getPath(true, false), "/clients")
if err != nil { if err != nil {
return "" return ""
@ -151,11 +129,17 @@ func (a HydraConnector) getClient() string {
if err != nil || len(clients) == 0 { if err != nil || len(clients) == 0 {
return "" return ""
} }
for _, c := range clients {
if c.(map[string]interface{})["client_name"].(string) == clientID {
return c.(map[string]interface{})["client_id"].(string)
}
}
return clients[0].(map[string]interface{})["client_id"].(string) return clients[0].(map[string]interface{})["client_id"].(string)
} }
func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Token, err error) { func (a HydraConnector) Login(clientID string, username string, cookies ...*http.Cookie) (t *Token, err error) {
clientID := a.getClient() fmt.Println("login", clientID, username)
clientID = a.getClient(clientID)
redirect, _, cookies, err := a.tryLog(username, a.getPath(false, true), redirect, _, cookies, err := a.tryLog(username, a.getPath(false, true),
"/auth?client_id="+clientID+"&response_type="+strings.ReplaceAll(a.ResponseType, " ", "%20")+"&scope="+strings.ReplaceAll(a.Scopes, " ", "%20")+"&state="+a.State, "/auth?client_id="+clientID+"&response_type="+strings.ReplaceAll(a.ResponseType, " ", "%20")+"&scope="+strings.ReplaceAll(a.Scopes, " ", "%20")+"&state="+a.State,
"login", cookies...) "login", cookies...)
@ -167,22 +151,8 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return nil, err return nil, err
} }
// problem with consent THERE we need to accept the consent challenge && get the token // problem with consent THERE we need to accept the consent challenge && get the token
_, err = a.Caller.CallRaw(http.MethodGet, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), "", map[string]interface{}{},
postBody, _ := json.Marshal(map[string]interface{}{}) "application/json", true, cookies...)
responseBody := bytes.NewBuffer(postBody)
req, _ := http.NewRequest(http.MethodGet, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), responseBody)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("X-Forwarded-Proto", "https")
for _, c := range cookies {
req.AddCookie(c)
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // No redirect, doesn't make sense; hydra redirect user to login page, we are not the user here due to wrong oauth flow implementation
},
}
_, err = client.Do(req)
if err != nil { if err != nil {
s := strings.Split(err.Error(), "\"") s := strings.Split(err.Error(), "\"")
if len(s) > 1 && strings.Contains(s[1], "access_token") { if len(s) > 1 && strings.Contains(s[1], "access_token") {
@ -195,15 +165,6 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
Username: username, Username: username,
} }
urls := url.Values{} urls := url.Values{}
// Using k8s secrets gen by hydra, eventually
clientID, clientSecret, err := a.getOAuth2Conf(conf.GetConfig().OAuth2ClientSecretNamespace, conf.GetConfig().OAuth2ClientSecretName)
if err == nil {
urls.Add("client_id", clientID)
urls.Add("client_secret", clientSecret)
}
// Fallback on manually set client secret
urls.Add("client_id", clientID) urls.Add("client_id", clientID)
urls.Add("client_secret", conf.GetConfig().ClientSecret) urls.Add("client_secret", conf.GetConfig().ClientSecret)
urls.Add("grant_type", "client_credentials") urls.Add("grant_type", "client_credentials")
@ -220,7 +181,7 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return nil, err return nil, err
} }
json.Unmarshal(b, &m) json.Unmarshal(b, &m)
pp := oclib.Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), oclib.LibDataEnum(oclib.PEER)) pp := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), false)
if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" { if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" {
return nil, errors.New("peer not found") return nil, errors.New("peer not found")
} }
@ -228,7 +189,8 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
now = now.Add(time.Duration(token.ExpiresIn) * time.Second) now = now.Add(time.Duration(token.ExpiresIn) * time.Second)
unix := now.Unix() unix := now.Unix()
c := claims.GetClaims().AddClaimsToToken(username, pp.Data[0].(*peer.Peer)) c := claims.GetClaims().AddClaimsToToken(clientID, username, pp.Data[0].(*peer.Peer))
fmt.Println("claims", c.Session.AccessToken)
c.Session.AccessToken["exp"] = unix c.Session.AccessToken["exp"] = unix
b, _ = json.Marshal(c) b, _ = json.Marshal(c)
@ -238,55 +200,8 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return token, nil return token, nil
} }
func (a HydraConnector) getOAuth2Conf(namespace string, secretName string) (string, string, error) { func (a HydraConnector) Logout(clientID string, token string, cookies ...*http.Cookie) (*Token, error) {
clientset, err := a.getClientset() clientID = a.getClient(clientID)
if err != nil {
return "", "", fmt.Errorf("error creating Kubernetes client: %v", err)
}
secret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
if err != nil {
return "", "", fmt.Errorf("error retrieving secret %s/%s: %v", namespace, secretName, err)
}
clientIDEncoded, found := secret.Data["CLIENT_ID"]
if !found {
return "", "", fmt.Errorf("CLIENT_ID key not found in secret")
}
clientSecretEncoded, found := secret.Data["CLIENT_SECRET"]
if !found {
return "", "", fmt.Errorf("CLIENT_SECRET key not found in secret")
}
clientID := string(clientIDEncoded)
clientSecret := string(clientSecretEncoded)
return clientID, clientSecret, nil
}
func (a HydraConnector) getClientset() (*kubernetes.Clientset, error) {
var config *rest.Config
var err error
// Check if running inside cluster
if _, inCluster := os.LookupEnv("KUBERNETES_SERVICE_HOST"); inCluster {
config, err = rest.InClusterConfig() // Use in-cluster config
} else {
kubeconfig := os.Getenv("KUBECONFIG") // Use local kubeconfig file
if kubeconfig == "" {
kubeconfig = clientcmd.RecommendedHomeFile
}
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
}
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}
func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, error) {
access := strings.Split(token, ".") access := strings.Split(token, ".")
if len(access) > 2 { if len(access) > 2 {
token = strings.Join(access[0:2], ".") token = strings.Join(access[0:2], ".")
@ -294,7 +209,7 @@ func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, e
p := a.getPath(false, true) + "/revoke" p := a.getPath(false, true) + "/revoke"
urls := url.Values{} urls := url.Values{}
urls.Add("token", token) urls.Add("token", token)
urls.Add("client_id", a.getClient()) urls.Add("client_id", clientID)
urls.Add("client_secret", conf.GetConfig().ClientSecret) urls.Add("client_secret", conf.GetConfig().ClientSecret)
_, err := a.Caller.CallForm(http.MethodPost, p, "", urls, "application/x-www-form-urlencoded", true) _, err := a.Caller.CallForm(http.MethodPost, p, "", urls, "application/x-www-form-urlencoded", true)
if err != nil { if err != nil {
@ -334,10 +249,9 @@ func (a HydraConnector) Introspect(token string, cookie ...*http.Cookie) (bool,
} }
func (a HydraConnector) getPath(isAdmin bool, isOauth bool) string { func (a HydraConnector) getPath(isAdmin bool, isOauth bool) string {
host := conf.GetConfig().AuthConnectPublicHost host := conf.GetConfig().AuthConnectorHost
port := fmt.Sprintf("%v", conf.GetConfig().AuthConnectorPort) port := fmt.Sprintf("%v", conf.GetConfig().AuthConnectorPort)
if isAdmin { if isAdmin {
host = conf.GetConfig().AuthConnectorHost
port = fmt.Sprintf("%v", conf.GetConfig().AuthConnectorAdminPort) + "/admin" port = fmt.Sprintf("%v", conf.GetConfig().AuthConnectorAdminPort) + "/admin"
} }
oauth := "" oauth := ""

View File

@ -31,8 +31,9 @@ var (
type conn interface { type conn interface {
Bind(bindDN, password string) error Bind(bindDN, password string) error
SearchUser(user string, attrs ...string) ([]map[string]interface{}, error) SearchRoles(attrs ...string) ([]map[string][]string, error)
SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error) SearchUser(user string, attrs ...string) ([]map[string][]string, error)
SearchUserRoles(user string, attrs ...string) ([]map[string][]string, error)
Close() error Close() error
} }
@ -78,7 +79,7 @@ type Client struct {
cache *freecache.Cache cache *freecache.Cache
} }
func (cli *Client) Authenticate(ctx context.Context, username, password string) (bool, error) { func (cli *Client) Authenticate(ctx context.Context, username string, password string) (bool, error) {
if username == "" || password == "" { if username == "" || password == "" {
return false, nil return false, nil
} }
@ -101,8 +102,8 @@ func (cli *Client) Authenticate(ctx context.Context, username, password string)
if details == nil { if details == nil {
return false, nil return false, nil
} }
a := details["dn"]
if err := cn.Bind(details["dn"].(string), password); err != nil { if err := cn.Bind(a[0], password); err != nil {
if err == errInvalidCredentials { if err == errInvalidCredentials {
return false, nil return false, nil
} }
@ -118,6 +119,21 @@ func (cli *Client) Authenticate(ctx context.Context, username, password string)
return true, nil return true, nil
} }
func (cli *Client) GetRoles(ctx context.Context) (map[string]LDAPRoles, error) {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
cn, ok := <-cli.connect(ctx)
cancel()
if !ok {
return map[string]LDAPRoles{}, errConnectionTimeout
}
defer cn.Close()
// Find a user DN by his or her username.
return cli.findRoles(cn, "dn", "member", "uniqueMember")
}
// Claim is the FindOIDCClaims result struct // Claim is the FindOIDCClaims result struct
type LDAPClaim struct { type LDAPClaim struct {
Code string // the root claim name Code string // the root claim name
@ -125,6 +141,10 @@ type LDAPClaim struct {
Value interface{} // the value Value interface{} // the value
} }
type LDAPRoles struct {
Members map[string][]string
}
// FindOIDCClaims finds all OIDC claims for a user. // FindOIDCClaims finds all OIDC claims for a user.
func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPClaim, error) { func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPClaim, error) {
if username == "" { if username == "" {
@ -193,11 +213,12 @@ func (cli *Client) FindOIDCClaims(ctx context.Context, username string) ([]LDAPC
roles := make(map[string]interface{}) roles := make(map[string]interface{})
for _, entry := range entries { for _, entry := range entries {
roleDN, ok := entry["dn"].(string) roleDNs, ok := entry["dn"]
if !ok || roleDN == "" { if !ok || len(roleDNs) == 0 {
log.Infow("No required LDAP attribute for a role", "ldapAttribute", "dn", "entry", entry) log.Infow("No required LDAP attribute for a role", "ldapAttribute", "dn", "entry", entry)
continue continue
} }
roleDN := roleDNs[0]
if entry[cli.RoleAttr] == nil { if entry[cli.RoleAttr] == nil {
log.Infow("No required LDAP attribute for a role", "ldapAttribute", cli.RoleAttr, "roleDN", roleDN) log.Infow("No required LDAP attribute for a role", "ldapAttribute", cli.RoleAttr, "roleDN", roleDN)
continue continue
@ -278,8 +299,79 @@ func (cli *Client) connect(ctx context.Context) <-chan conn {
return ch return ch
} }
func (cli *Client) findRoles(cn conn, attrs ...string) (map[string]LDAPRoles, error) {
if cli.BindDN != "" {
// We need to login to a LDAP server with a service account for retrieving user data.
if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil {
return map[string]LDAPRoles{}, errors.New(err.Error() + " : failed to login to a LDAP woth a service account")
}
}
entries, err := cn.SearchRoles(attrs...)
fmt.Println("entries", entries)
if err != nil {
return map[string]LDAPRoles{}, err
}
claims := map[string]LDAPRoles{}
for _, entry := range entries {
roleDNs, ok := entry["dn"]
if !ok || len(roleDNs) == 0 {
continue
}
roleDN := roleDNs[0]
// Ensure that a role's DN is inside of the role's base DN.
// It's sufficient to compare the DN's suffix with the base DN.
n, k := len(roleDN), len(cli.RoleBaseDN)
if n < k || !strings.EqualFold(roleDN[n-k:], cli.RoleBaseDN) {
panic("You should never see that")
}
// The DN without the role's base DN must contain a CN and OU
// where the CN is for uniqueness only, and the OU is an application id.
path := strings.Split(roleDN[:n-k-1], ",")
if len(path) != 2 {
continue
}
appID := path[1][len("OU="):]
if _, ok := claims[appID]; !ok {
claims[appID] = LDAPRoles{
Members: map[string][]string{},
}
}
role := path[0][len("cn="):]
if claims[appID].Members[role] == nil {
claims[appID].Members[role] = []string{}
}
fmt.Println("entry", entry)
memberDNs, ok := entry["member"]
for _, memberDN := range memberDNs {
if !ok || memberDN == "" {
continue
}
path = strings.Split(memberDN[:n-k-1], ",")
if len(path) < 1 {
continue
}
member := strings.Split(path[0][len("uid="):], ",")
claims[appID].Members[role] = append(claims[appID].Members[role], member[0])
}
memberDNs, ok = entry["uniqueMember"]
for _, memberDN := range memberDNs {
if !ok || memberDN == "" {
continue
}
path = strings.Split(memberDN[:n-k-1], ",")
if len(path) < 1 {
continue
}
member := strings.Split(path[0][len("uid="):], ",")
claims[appID].Members[role] = append(claims[appID].Members[role], member[0])
}
}
return claims, nil
}
// findBasicUserDetails finds user's LDAP attributes that were specified. It returns nil if no such user. // findBasicUserDetails finds user's LDAP attributes that were specified. It returns nil if no such user.
func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string) (map[string]interface{}, error) { func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string) (map[string][]string, error) {
if cli.BindDN != "" { if cli.BindDN != "" {
// We need to login to a LDAP server with a service account for retrieving user data. // We need to login to a LDAP server with a service account for retrieving user data.
if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil { if err := cn.Bind(cli.BindDN, cli.BindPass); err != nil {
@ -298,7 +390,7 @@ func (cli *Client) findBasicUserDetails(cn conn, username string, attrs []string
var ( var (
entry = entries[0] entry = entries[0]
details = make(map[string]interface{}) details = make(map[string][]string)
) )
for _, attr := range attrs { for _, attr := range attrs {
if v, ok := entry[attr]; ok { if v, ok := entry[attr]; ok {
@ -349,35 +441,40 @@ func (c *ldapConn) Bind(bindDN, password string) error {
return err return err
} }
func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string]interface{}, error) { func (c *ldapConn) SearchUser(user string, attrs ...string) ([]map[string][]string, error) {
query := fmt.Sprintf( query := fmt.Sprintf(
"(&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))"+ "(&(|(objectClass=organizationalPerson)(objectClass=inetOrgPerson))"+
"(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))", user) "(|(uid=%[1]s)(mail=%[1]s)(userPrincipalName=%[1]s)(sAMAccountName=%[1]s)))", user)
return c.searchEntries(c.BaseDN, query, attrs) return c.searchEntries(c.BaseDN, query, attrs)
} }
func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string]interface{}, error) { func (c *ldapConn) SearchUserRoles(user string, attrs ...string) ([]map[string][]string, error) {
query := fmt.Sprintf("(|"+ query := fmt.Sprintf("(|"+
"(&(|(objectClass=group)(objectClass=groupOfNames))(member=%[1]s))"+ "(&(|(objectClass=group)(objectClass=groupOfNames)(objectClass=groupofnames))(member=%[1]s))"+
"(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s))"+ "(&(objectClass=groupOfUniqueNames)(uniqueMember=%[1]s))"+
")", user) ")", user)
return c.searchEntries(c.RoleBaseDN, query, attrs) return c.searchEntries(c.RoleBaseDN, query, attrs)
} }
func (c *ldapConn) SearchRoles(attrs ...string) ([]map[string][]string, error) {
query := "(|(&(|(objectClass=group)(objectClass=groupOfNames)(objectClass=groupofnames))))"
return c.searchEntries(c.RoleBaseDN, query, attrs)
}
// searchEntries executes a LDAP query, and returns a result as entries where each entry is mapping of LDAP attributes. // searchEntries executes a LDAP query, and returns a result as entries where each entry is mapping of LDAP attributes.
func (c *ldapConn) searchEntries(baseDN, query string, attrs []string) ([]map[string]interface{}, error) { func (c *ldapConn) searchEntries(baseDN, query string, attrs []string) ([]map[string][]string, error) {
req := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, query, attrs, nil) req := ldap.NewSearchRequest(baseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, query, attrs, nil)
res, err := c.Search(req) res, err := c.Search(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var entries []map[string]interface{} var entries []map[string][]string
for _, v := range res.Entries { for _, v := range res.Entries {
entry := map[string]interface{}{"dn": v.DN} entry := map[string][]string{"dn": []string{v.DN}}
for _, attr := range v.Attributes { for _, attr := range v.Attributes {
// We need the first value only for the named attribute. // We need the first value only for the named attribute.
entry[attr.Name] = attr.Values[0] entry[attr.Name] = attr.Values
} }
entries = append(entries, entry) entries = append(entries, entry)
} }

View File

@ -8,7 +8,7 @@ import (
// Tokenizer interface // Tokenizer interface
type ClaimService interface { type ClaimService interface {
AddClaimsToToken(userId string, peer *peer.Peer) Claims AddClaimsToToken(clientID string, userId string, peer *peer.Peer) Claims
DecodeClaimsInToken(host string, method string, forward string, sessionClaims Claims, publicKey string, external bool) (bool, error) DecodeClaimsInToken(host string, method string, forward string, sessionClaims Claims, publicKey string, external bool) (bool, error)
} }

View File

@ -4,6 +4,7 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt"
"oc-auth/conf" "oc-auth/conf"
"oc-auth/infrastructure/perms_connectors" "oc-auth/infrastructure/perms_connectors"
"oc-auth/infrastructure/utils" "oc-auth/infrastructure/utils"
@ -119,21 +120,23 @@ func (h HydraClaims) DecodeClaimsInToken(host string, method string, forward str
Relation: "permits" + strings.ToUpper(meth.String()), Relation: "permits" + strings.ToUpper(meth.String()),
Object: p.(string), Object: p.(string),
} }
return perms_connectors.GetPermissionConnector().CheckPermission(perm, nil, true), nil return perms_connectors.GetPermissionConnector("").CheckPermission(perm, nil, true), nil
} }
} }
return false, errors.New("no permission found") return false, errors.New("no permission found")
} }
// add claims to token method of HydraTokenizer // add claims to token method of HydraTokenizer
func (h HydraClaims) AddClaimsToToken(userId string, p *peer.Peer) Claims { func (h HydraClaims) AddClaimsToToken(clientID string, userId string, p *peer.Peer) Claims {
claims := Claims{} claims := Claims{}
perms, err := perms_connectors.KetoConnector{}.GetPermissionByUser(userId, true) perms, err := perms_connectors.KetoConnector{}.GetPermissionByUser(userId, true)
if err != nil { if err != nil {
return claims return claims
} }
claims.Session.AccessToken = make(map[string]interface{}) claims.Session.AccessToken = make(map[string]interface{})
claims.Session.IDToken = make(map[string]interface{}) claims.Session.IDToken = make(map[string]interface{})
fmt.Println("PERMS err 1", perms, err)
for _, perm := range perms { for _, perm := range perms {
key, err := h.generateKey(strings.ReplaceAll(perm.Relation, "permits", ""), perm.Subject) key, err := h.generateKey(strings.ReplaceAll(perm.Relation, "permits", ""), perm.Subject)
if err != nil { if err != nil {
@ -145,15 +148,15 @@ func (h HydraClaims) AddClaimsToToken(userId string, p *peer.Peer) Claims {
if err != nil { if err != nil {
return claims return claims
} }
claims.Session.IDToken["username"] = userId
claims.Session.IDToken["peer_id"] = p.UUID claims.Session.IDToken["peer_id"] = p.UUID
// we should get group from user // we should get group from user
groups, err := perms_connectors.KetoConnector{}.GetGroupByUser(userId) groups, err := perms_connectors.KetoConnector{}.GetGroupByUser(userId)
if err != nil { if err != nil {
return claims return claims
} }
claims.Session.IDToken["client_id"] = clientID
claims.Session.IDToken["groups"] = groups claims.Session.IDToken["groups"] = groups
claims.Session.IDToken["signature"] = sign claims.Session.IDToken["signature"] = sign
return claims return claims
} }
// add signature in the token MISSING

View File

@ -10,8 +10,8 @@ func GetAuthConnector() auth_connectors.AuthConnector {
return auth_connectors.GetAuthConnector() return auth_connectors.GetAuthConnector()
} }
func GetPermissionConnector() perms_connectors.PermConnector { func GetPermissionConnector(client string) perms_connectors.PermConnector {
return perms_connectors.GetPermissionConnector() return perms_connectors.GetPermissionConnector(client)
} }
func GetClaims() claims.ClaimService { func GetClaims() claims.ClaimService {

View File

@ -6,24 +6,29 @@ import (
"fmt" "fmt"
"oc-auth/conf" "oc-auth/conf"
"oc-auth/infrastructure/utils" "oc-auth/infrastructure/utils"
"strings"
oclib "cloud.o-forge.io/core/oc-lib" oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
type KetoConnector struct{} type KetoConnector struct {
Client string
}
func (k KetoConnector) SetClient(client string) {
k.Client = client
}
func (k KetoConnector) namespace() string { func (k KetoConnector) namespace() string {
return "open-cloud" return "open-cloud"
} }
func (k KetoConnector) scope() string { func (k KetoConnector) scope() string {
return "oc-auth" return "oc-auth-realm"
} }
func (f KetoConnector) permToQuery(perm Permission, permDependancies *Permission) string { func (f KetoConnector) permToQuery(perm Permission, permDependancies *Permission) string {
n := "?namespace=" + perm.Namespace() n := "?namespace=" + f.namespace()
if perm.Object != "" { if perm.Object != "" {
n += "&object=" + perm.Object n += "&object=" + perm.Object
} }
@ -189,6 +194,7 @@ func (k KetoConnector) GetPermissionByRole(roleID string) ([]Permission, error)
} }
func (k KetoConnector) GetPermissionByUser(userID string, internal bool) ([]Permission, error) { func (k KetoConnector) GetPermissionByUser(userID string, internal bool) ([]Permission, error) {
roles, err := k.get("", "member", userID) roles, err := k.get("", "member", userID)
fmt.Println("ROLES", roles, err)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -235,7 +241,7 @@ func (k KetoConnector) get(object string, relation string, subject string) ([]Pe
return t, nil return t, nil
} }
func (k KetoConnector) binds(subject string, relation string, object string) (string, int, error) { func (k KetoConnector) binds(object string, relation string, subject string) (string, int, error) {
_, code, err := k.createRelationShip(object, relation, subject, nil) _, code, err := k.createRelationShip(object, relation, subject, nil)
if err != nil { if err != nil {
return object, code, err return object, code, err
@ -244,6 +250,7 @@ func (k KetoConnector) binds(subject string, relation string, object string) (st
} }
func (k KetoConnector) BindRole(userID string, roleID string) (string, int, error) { func (k KetoConnector) BindRole(userID string, roleID string) (string, int, error) {
fmt.Println("BIND ROLE", userID, roleID)
return k.binds(userID, "member", roleID) return k.binds(userID, "member", roleID)
} }
@ -324,9 +331,6 @@ func (k KetoConnector) UnBindPermission(roleID string, permID string, relation s
} }
func (k KetoConnector) createRelationShip(object string, relation string, subject string, subPerm *Permission) (*Permission, int, error) { func (k KetoConnector) createRelationShip(object string, relation string, subject string, subPerm *Permission) (*Permission, int, error) {
exist, err := k.get(object, relation, subject) exist, err := k.get(object, relation, subject)
if strings.Contains(subject, "/workflow/:id") {
fmt.Println("subject", subject, relation, exist, err)
}
if err == nil && len(exist) > 0 { if err == nil && len(exist) > 0 {
return nil, 409, errors.New("Relation already exist") return nil, 409, errors.New("Relation already exist")
} }
@ -338,11 +342,11 @@ func (k KetoConnector) createRelationShip(object string, relation string, subjec
if err != nil { if err != nil {
return nil, code, err return nil, code, err
} }
body["subject_set"] = map[string]interface{}{"namespace": s.Namespace(), "object": s.Object, "relation": s.Relation, "subject_id": s.Subject} body["subject_set"] = map[string]interface{}{"namespace": k.namespace(), "object": s.Object, "relation": s.Relation, "subject_id": s.Subject}
} }
host := conf.GetConfig().PermissionConnectorHost host := conf.GetConfig().PermissionConnectorHost
port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorAdminPort) port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorAdminPort)
b, err := caller.CallPut("http://"+host+":"+port, "/admin/relation-tuples", body) b, err := caller.CallPut("http://"+host+":"+port, "/relation-tuples", body)
if err != nil { if err != nil {
log := oclib.GetLogger() log := oclib.GetLogger()
log.Error().Msg(err.Error()) log.Error().Msg(err.Error())

View File

@ -1,6 +1,8 @@
package perms_connectors package perms_connectors
import ( import (
"oc-auth/conf"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
) )
@ -21,6 +23,7 @@ func (k Permission) Scope() string {
type PermConnector interface { type PermConnector interface {
Status() tools.State Status() tools.State
SetClient(scope string)
CheckPermission(perm Permission, permDependancies *Permission, internal bool) bool CheckPermission(perm Permission, permDependancies *Permission, internal bool) bool
BindRole(userID string, roleID string) (string, int, error) BindRole(userID string, roleID string) (string, int, error)
BindGroup(userID string, groupID string) (string, int, error) BindGroup(userID string, groupID string) (string, int, error)
@ -51,6 +54,6 @@ var c = map[string]PermConnector{
"keto": KetoConnector{}, "keto": KetoConnector{},
} }
func GetPermissionConnector() PermConnector { func GetPermissionConnector(scope string) PermConnector {
return c["keto"] return c[conf.GetConfig().PermissionConnectorHost]
} }

View File

@ -0,0 +1,78 @@
version: "3"
services:
hydra-client-2:
image: oryd/hydra:v2.2.0
container_name: hydra-client-2
environment:
HYDRA_ADMIN_URL: http://hydra-2:4445
ORY_SDK_URL: http://hydra-2:4445
command:
- create
- oauth2-client
- --skip-tls-verify
- --name
- test-client
- --secret
- oc-auth-got-secret
- --response-type
- id_token,token,code
- --grant-type
- implicit,refresh_token,authorization_code,client_credentials
- --scope
- openid,profile,email,roles
- --token-endpoint-auth-method
- client_secret_post
- --redirect-uri
- http://localhost:3000
networks:
- hydra-net
- catalog
deploy:
restart_policy:
condition: none
depends_on:
- hydra-2
healthcheck:
test: ["CMD", "curl", "-f", "http://hydra-2:4445"]
interval: 10s
timeout: 10s
retries: 10
hydra-2:
container_name: hydra-2
image: oryd/hydra:v2.2.0
environment:
SECRETS_SYSTEM: oc-auth-got-secret
LOG_LEAK_SENSITIVE_VALUES: true
URLS_SELF_ISSUER: http://hydra-2:4444
URLS_SELF_PUBLIC: http://hydra-2:4444
WEBFINGER_OIDC_DISCOVERY_SUPPORTED_SCOPES: profile,email,phone,roles
WEBFINGER_OIDC_DISCOVERY_SUPPORTED_CLAIMS: name,family_name,given_name,nickname,email,phone_number
DSN: memory
command: serve all --dev
networks:
- hydra-net
- catalog
ports:
- "4446:4444"
- "4447:4445"
deploy:
restart_policy:
condition: on-failure
ldap-2:
image: pgarrett/ldap-alpine
container_name: ldap-2
volumes:
- "./ldap-2.ldif:/ldif/ldap.ldif"
networks:
- hydra-net
- catalog
ports:
- "389:389"
deploy:
restart_policy:
condition: on-failure
networks:
hydra-net:
catalog:
external: true

24
ldap-hydra/ldap-2.ldif Normal file
View File

@ -0,0 +1,24 @@
dn: uid=admin2,ou=Users,dc=example,dc=com
objectClass: inetOrgPerson
cn: Admin2
sn: Istrator
uid: admin2
userPassword: admin2
mail: admin2@example.com
ou: Users
dn: ou=AppRoles,dc=example,dc=com
objectClass: organizationalunit
ou: AppRoles
description: AppRoles
dn: ou=App1,ou=AppRoles,dc=example,dc=com
objectClass: organizationalunit
ou: App1
description: App1
dn: cn=traveler,ou=App1,ou=AppRoles,dc=example,dc=com
objectClass: groupofnames
cn: traveler
description: traveler
member: uid=admin2,ou=Users,dc=example,dc=com

48
main.go
View File

@ -1,9 +1,12 @@
package main package main
import ( import (
"context"
"errors" "errors"
"fmt"
"oc-auth/conf" "oc-auth/conf"
"oc-auth/infrastructure" "oc-auth/infrastructure"
auth_connectors "oc-auth/infrastructure/auth_connector"
_ "oc-auth/routers" _ "oc-auth/routers"
"os" "os"
"strconv" "strconv"
@ -14,7 +17,6 @@ import (
"cloud.o-forge.io/core/oc-lib/models/utils" "cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools" "cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web" beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/server/web/filter/cors"
) )
const appname = "oc-auth" const appname = "oc-auth"
@ -33,11 +35,9 @@ func main() {
conf.GetConfig().PublicKeyPath = o.GetStringDefault("PUBLIC_KEY_PATH", "./pem/public.pem") conf.GetConfig().PublicKeyPath = o.GetStringDefault("PUBLIC_KEY_PATH", "./pem/public.pem")
conf.GetConfig().PrivateKeyPath = o.GetStringDefault("PRIVATE_KEY_PATH", "./pem/private.pem") conf.GetConfig().PrivateKeyPath = o.GetStringDefault("PRIVATE_KEY_PATH", "./pem/private.pem")
conf.GetConfig().ClientSecret = o.GetStringDefault("CLIENT_SECRET", "oc-auth-got-secret") conf.GetConfig().ClientSecret = o.GetStringDefault("CLIENT_SECRET", "oc-auth-got-secret")
conf.GetConfig().OAuth2ClientSecretName = o.GetStringDefault("OAUTH2_CLIENT_SECRET_NAME", "oc-oauth2-client-secret")
conf.GetConfig().OAuth2ClientSecretNamespace = o.GetStringDefault("NAMESPACE", "default")
conf.GetConfig().Auth = o.GetStringDefault("AUTH", "hydra") conf.GetConfig().Auth = o.GetStringDefault("AUTH", "hydra")
conf.GetConfig().AuthConnectorHost = o.GetStringDefault("AUTH_CONNECTOR_HOST", "localhost") conf.GetConfig().AuthConnectorHost = o.GetStringDefault("AUTH_CONNECTOR_HOST", "localhost")
conf.GetConfig().AuthConnectPublicHost = o.GetStringDefault("AUTH_CONNECTOR_PUBLIC_HOST", "localhost")
conf.GetConfig().AuthConnectorPort = o.GetIntDefault("AUTH_CONNECTOR_PORT", 4444) conf.GetConfig().AuthConnectorPort = o.GetIntDefault("AUTH_CONNECTOR_PORT", 4444)
conf.GetConfig().AuthConnectorAdminPort = o.GetIntDefault("AUTH_CONNECTOR_ADMIN_PORT", 4445) conf.GetConfig().AuthConnectorAdminPort = o.GetIntDefault("AUTH_CONNECTOR_ADMIN_PORT", 4445)
conf.GetConfig().PermissionConnectorHost = o.GetStringDefault("PERMISSION_CONNECTOR_HOST", "keto") conf.GetConfig().PermissionConnectorHost = o.GetStringDefault("PERMISSION_CONNECTOR_HOST", "keto")
@ -45,6 +45,7 @@ func main() {
conf.GetConfig().PermissionConnectorAdminPort = o.GetIntDefault("PERMISSION_CONNECTOR_ADMIN_PORT", 4467) conf.GetConfig().PermissionConnectorAdminPort = o.GetIntDefault("PERMISSION_CONNECTOR_ADMIN_PORT", 4467)
// config LDAP // config LDAP
conf.GetConfig().SourceMode = o.GetStringDefault("SOURCE_MODE", "ldap")
conf.GetConfig().LDAPEndpoints = o.GetStringDefault("LDAP_ENDPOINTS", "ldap:389") conf.GetConfig().LDAPEndpoints = o.GetStringDefault("LDAP_ENDPOINTS", "ldap:389")
conf.GetConfig().LDAPBindDN = o.GetStringDefault("LDAP_BINDDN", "cn=admin,dc=example,dc=com") conf.GetConfig().LDAPBindDN = o.GetStringDefault("LDAP_BINDDN", "cn=admin,dc=example,dc=com")
conf.GetConfig().LDAPBindPW = o.GetStringDefault("LDAP_BINDPW", "password") conf.GetConfig().LDAPBindPW = o.GetStringDefault("LDAP_BINDPW", "password")
@ -54,17 +55,36 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
generateRole()
discovery() discovery()
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowAllOrigins: true,
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length", "Content-Type"},
AllowCredentials: true,
}))
beego.Run() beego.Run()
} }
func generateRole() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered in f", r)
}
}()
// if from ldap, create roles from ldap
if conf.GetConfig().SourceMode == "ldap" {
ldap := auth_connectors.New()
roles, err := ldap.GetRoles(context.Background())
if err != nil {
panic(err)
}
fmt.Println("ROLE", roles)
for _, role := range roles {
for r, m := range role.Members {
infrastructure.GetPermissionConnector("").CreateRole(r)
for _, p := range m {
infrastructure.GetPermissionConnector("").BindRole(r, p)
}
}
}
}
}
func generateSelfPeer() error { func generateSelfPeer() error {
// TODO check if files at private & public path are set // TODO check if files at private & public path are set
// check if files at private & public path are set // check if files at private & public path are set
@ -75,7 +95,7 @@ func generateSelfPeer() error {
return errors.New("public key path does not exist") return errors.New("public key path does not exist")
} }
// check if peer already exists // check if peer already exists
p := oclib.Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), oclib.LibDataEnum(oclib.PEER)) p := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), false)
file := "" file := ""
f, err := os.ReadFile(conf.GetConfig().PublicKeyPath) f, err := os.ReadFile(conf.GetConfig().PublicKeyPath)
if err != nil { if err != nil {
@ -100,7 +120,7 @@ func generateSelfPeer() error {
PublicKey: file, PublicKey: file,
State: peer.SELF, State: peer.SELF,
} }
data := oclib.StoreOne(oclib.LibDataEnum(oclib.PEER), peer.Serialize()) data := oclib.NewRequest(oclib.LibDataEnum(oclib.PEER), "", "", []string{}, nil).StoreOne(peer.Serialize(peer))
if data.Err != "" { if data.Err != "" {
return errors.New(data.Err) return errors.New(data.Err)
} }
@ -109,7 +129,7 @@ func generateSelfPeer() error {
func discovery() { func discovery() {
api := tools.API{} api := tools.API{}
conn := infrastructure.GetPermissionConnector() conn := infrastructure.GetPermissionConnector("")
conn.CreateRole(conf.GetConfig().AdminRole) conn.CreateRole(conf.GetConfig().AdminRole)
conn.BindRole(conf.GetConfig().AdminRole, "admin") conn.BindRole(conf.GetConfig().AdminRole, "admin")

BIN
oc-auth Executable file

Binary file not shown.

View File

@ -81,7 +81,7 @@ func init() {
beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"], beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"],
beego.ControllerComments{ beego.ControllerComments{
Method: "InternalAuthForward", Method: "InternaisDraftlAuthForward",
Router: `/forward`, Router: `/forward`,
AllowHTTPMethods: []string{"get"}, AllowHTTPMethods: []string{"get"},
MethodParams: param.Make(), MethodParams: param.Make(),
@ -99,8 +99,8 @@ func init() {
beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"], beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"],
beego.ControllerComments{ beego.ControllerComments{
Method: "LoginLDAP", Method: "Login",
Router: `/ldap/login`, Router: `/login`,
AllowHTTPMethods: []string{"post"}, AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(), MethodParams: param.Make(),
Filters: nil, Filters: nil,
@ -108,8 +108,8 @@ func init() {
beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"], beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"],
beego.ControllerComments{ beego.ControllerComments{
Method: "LogOutLDAP", Method: "LogOut",
Router: `/ldap/logout`, Router: `/logout`,
AllowHTTPMethods: []string{"delete"}, AllowHTTPMethods: []string{"delete"},
MethodParams: param.Make(), MethodParams: param.Make(),
Filters: nil, Filters: nil,

View File

@ -191,7 +191,7 @@
"parameters": [ "parameters": [
{ {
"in": "path", "in": "path",
"name": "group_id", "name": "user_id",
"description": "The group_id you want to unbind", "description": "The group_id you want to unbind",
"required": true, "required": true,
"type": "string" "type": "string"
@ -233,7 +233,7 @@
} }
} }
}, },
"/ldap/login": { "/login": {
"post": { "post": {
"tags": [ "tags": [
"oc-auth/controllersOAuthController" "oc-auth/controllersOAuthController"
@ -249,6 +249,13 @@
"schema": { "schema": {
"$ref": "#/definitions/models.workflow" "$ref": "#/definitions/models.workflow"
} }
},
{
"in": "query",
"name": "client_id",
"description": "the client_id you want to get",
"required": true,
"type": "string"
} }
], ],
"responses": { "responses": {
@ -258,7 +265,7 @@
} }
} }
}, },
"/ldap/logout": { "/logout": {
"delete": { "delete": {
"tags": [ "tags": [
"oc-auth/controllersOAuthController" "oc-auth/controllersOAuthController"
@ -271,6 +278,13 @@
"name": "Authorization", "name": "Authorization",
"description": "auth token", "description": "auth token",
"type": "string" "type": "string"
},
{
"in": "query",
"name": "client_id",
"description": "the client_id you want to get",
"required": true,
"type": "string"
} }
], ],
"responses": { "responses": {
@ -465,6 +479,13 @@
"schema": { "schema": {
"$ref": "#/definitions/models.Token" "$ref": "#/definitions/models.Token"
} }
},
{
"in": "query",
"name": "client_id",
"description": "the client_id you want to get",
"required": true,
"type": "string"
} }
], ],
"responses": { "responses": {

View File

@ -119,7 +119,7 @@ paths:
operationId: GroupController.UnBind operationId: GroupController.UnBind
parameters: parameters:
- in: path - in: path
name: group_id name: user_id
description: The group_id you want to unbind description: The group_id you want to unbind
required: true required: true
type: string type: string
@ -175,7 +175,7 @@ paths:
responses: responses:
"200": "200":
description: '{string}' description: '{string}'
/ldap/login: /login:
post: post:
tags: tags:
- oc-auth/controllersOAuthController - oc-auth/controllersOAuthController
@ -190,10 +190,15 @@ paths:
required: true required: true
schema: schema:
$ref: '#/definitions/models.workflow' $ref: '#/definitions/models.workflow'
- in: query
name: client_id
description: the client_id you want to get
required: true
type: string
responses: responses:
"200": "200":
description: '{string}' description: '{string}'
/ldap/logout: /logout:
delete: delete:
tags: tags:
- oc-auth/controllersOAuthController - oc-auth/controllersOAuthController
@ -206,6 +211,11 @@ paths:
name: Authorization name: Authorization
description: auth token description: auth token
type: string type: string
- in: query
name: client_id
description: the client_id you want to get
required: true
type: string
responses: responses:
"200": "200":
description: '{string}' description: '{string}'
@ -350,6 +360,11 @@ paths:
required: true required: true
schema: schema:
$ref: '#/definitions/models.Token' $ref: '#/definitions/models.Token'
- in: query
name: client_id
description: the client_id you want to get
required: true
type: string
responses: responses:
"200": "200":
description: '{string}' description: '{string}'