mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-06 04:09:40 -06:00
190 lines
5.9 KiB
Go
190 lines
5.9 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
revauser "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
|
types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1"
|
|
"github.com/cs3org/reva/pkg/token/manager/jwt"
|
|
acc "github.com/owncloud/ocis/accounts/pkg/proto/v0"
|
|
"github.com/owncloud/ocis/ocis-pkg/log"
|
|
"github.com/owncloud/ocis/ocis-pkg/oidc"
|
|
settings "github.com/owncloud/ocis/settings/pkg/proto/v0"
|
|
)
|
|
|
|
func getAccount(l log.Logger, ac acc.AccountsService, query string) (account *acc.Account, status int) {
|
|
resp, err := ac.ListAccounts(context.Background(), &acc.ListAccountsRequest{
|
|
Query: query,
|
|
PageSize: 2,
|
|
})
|
|
|
|
if err != nil {
|
|
l.Error().Err(err).Str("query", query).Msgf("Error fetching from accounts-service")
|
|
status = http.StatusInternalServerError
|
|
return
|
|
}
|
|
|
|
if len(resp.Accounts) <= 0 {
|
|
l.Error().Str("query", query).Msgf("Account not found")
|
|
status = http.StatusNotFound
|
|
return
|
|
}
|
|
|
|
if len(resp.Accounts) > 1 {
|
|
l.Error().Str("query", query).Msgf("More than one account found. Not logging user in.")
|
|
status = http.StatusForbidden
|
|
return
|
|
}
|
|
|
|
account = resp.Accounts[0]
|
|
return
|
|
}
|
|
|
|
func createAccount(l log.Logger, claims *oidc.StandardClaims, ac acc.AccountsService) (*acc.Account, int) {
|
|
// TODO check if fields are missing.
|
|
req := &acc.CreateAccountRequest{
|
|
Account: &acc.Account{
|
|
DisplayName: claims.DisplayName,
|
|
PreferredName: claims.PreferredUsername,
|
|
OnPremisesSamAccountName: claims.PreferredUsername,
|
|
Mail: claims.Email,
|
|
CreationType: "LocalAccount",
|
|
AccountEnabled: true,
|
|
// TODO assign uidnumber and gidnumber? better do that in ocis-accounts as it can keep track of the next numbers
|
|
},
|
|
}
|
|
created, err := ac.CreateAccount(context.Background(), req)
|
|
if err != nil {
|
|
l.Error().Err(err).Interface("account", req.Account).Msg("could not create account")
|
|
return nil, http.StatusInternalServerError
|
|
}
|
|
|
|
return created, 0
|
|
}
|
|
|
|
// AccountUUID provides a middleware which mints a jwt and adds it to the proxied request based
|
|
// on the oidc-claims
|
|
func AccountUUID(opts ...Option) func(next http.Handler) http.Handler {
|
|
opt := newOptions(opts...)
|
|
|
|
return func(next http.Handler) http.Handler {
|
|
// TODO: handle error
|
|
tokenManager, err := jwt.New(map[string]interface{}{
|
|
"secret": opt.TokenManagerConfig.JWTSecret,
|
|
"expires": int64(60),
|
|
})
|
|
if err != nil {
|
|
opt.Logger.Fatal().Err(err).Msgf("Could not initialize token-manager")
|
|
}
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
l := opt.Logger
|
|
claims := oidc.FromContext(r.Context())
|
|
if claims == nil {
|
|
next.ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
var account *acc.Account
|
|
var status int
|
|
if claims.Email != "" {
|
|
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("mail eq '%s'", strings.ReplaceAll(claims.Email, "'", "''")))
|
|
} else if claims.PreferredUsername != "" {
|
|
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("preferred_name eq '%s'", strings.ReplaceAll(claims.PreferredUsername, "'", "''")))
|
|
} else if claims.OcisID != "" {
|
|
account, status = getAccount(l, opt.AccountsClient, fmt.Sprintf("id eq '%s'", strings.ReplaceAll(claims.OcisID, "'", "''")))
|
|
} else {
|
|
// TODO allow lookup by custom claim, eg an id ... or sub
|
|
l.Error().Err(err).Msgf("Could not lookup account, no mail or preferred_username claim set")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|
|
if status != 0 || account == nil {
|
|
if opt.AutoprovisionAccounts && status == http.StatusNotFound {
|
|
account, status = createAccount(l, claims, opt.AccountsClient)
|
|
if status != 0 {
|
|
w.WriteHeader(status)
|
|
return
|
|
}
|
|
} else {
|
|
w.WriteHeader(status)
|
|
return
|
|
}
|
|
}
|
|
if !account.AccountEnabled {
|
|
l.Debug().Interface("account", account).Msg("account is disabled")
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
groups := make([]string, len(account.MemberOf))
|
|
for i := range account.MemberOf {
|
|
// reva needs the unix group name
|
|
groups[i] = account.MemberOf[i].OnPremisesSamAccountName
|
|
}
|
|
|
|
// fetch active roles from ocis-settings
|
|
assignmentResponse, err := opt.SettingsRoleService.ListRoleAssignments(r.Context(), &settings.ListRoleAssignmentsRequest{AccountUuid: account.Id})
|
|
roleIDs := make([]string, 0)
|
|
if err != nil {
|
|
l.Err(err).Str("accountID", account.Id).Msg("failed to fetch role assignments")
|
|
} else {
|
|
for _, assignment := range assignmentResponse.Assignments {
|
|
roleIDs = append(roleIDs, assignment.RoleId)
|
|
}
|
|
}
|
|
|
|
l.Debug().Interface("claims", claims).Interface("account", account).Msgf("Associated claims with uuid")
|
|
user := &revauser.User{
|
|
Id: &revauser.UserId{
|
|
OpaqueId: account.Id,
|
|
Idp: claims.Iss,
|
|
},
|
|
Username: account.OnPremisesSamAccountName,
|
|
DisplayName: account.DisplayName,
|
|
Mail: account.Mail,
|
|
MailVerified: account.ExternalUserState == "" || account.ExternalUserState == "Accepted",
|
|
Groups: groups,
|
|
Opaque: &types.Opaque{
|
|
Map: map[string]*types.OpaqueEntry{},
|
|
},
|
|
}
|
|
|
|
user.Opaque.Map["uid"] = &types.OpaqueEntry{
|
|
Decoder: "plain",
|
|
Value: []byte(strconv.FormatInt(account.UidNumber, 10)),
|
|
}
|
|
user.Opaque.Map["gid"] = &types.OpaqueEntry{
|
|
Decoder: "plain",
|
|
Value: []byte(strconv.FormatInt(account.GidNumber, 10)),
|
|
}
|
|
|
|
// encode roleIDs as json string
|
|
roleIDsJSON, jsonErr := json.Marshal(roleIDs)
|
|
if jsonErr != nil {
|
|
l.Err(jsonErr).Str("accountID", account.Id).Msg("failed to marshal roleIDs into json")
|
|
} else {
|
|
user.Opaque.Map["roles"] = &types.OpaqueEntry{
|
|
Decoder: "json",
|
|
Value: roleIDsJSON,
|
|
}
|
|
}
|
|
|
|
token, err := tokenManager.MintToken(r.Context(), user)
|
|
|
|
if err != nil {
|
|
l.Error().Err(err).Msgf("Could not mint token")
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
r.Header.Set("x-access-token", token)
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
}
|