Skip to content

Commit

Permalink
feat: implement group ACLs
Browse files Browse the repository at this point in the history
This allows for access control on a per client basis. You can either
allow or deny access for a list of groups
  • Loading branch information
theSuess committed Jun 23, 2021
1 parent 9592b0d commit b80c3cd
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 6 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,9 @@ clients:

If you do not want to provide a plaintext secret, you can also provide the
secret as an already hashed bcrypt2 value

#### Group ACLs

In case you want your client to be only available for members of a certain
group, you can populate the `allowGroups` or `denyGroups` fields in the client
config. This will either allow or deny access on a client basis.
7 changes: 7 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ type OIDCProvider struct {
discourseSecret string
privateKey *rsa.PrivateKey
}

type DistrustClient struct {
fosite.DefaultClient
AllowGroups []string
DenyGroups []string
}

type InFlightRequest struct {
Nonce int
Ar fosite.AuthorizeRequester
Expand Down
37 changes: 37 additions & 0 deletions auth/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ package auth

import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"net/url"
"strconv"
"strings"
"time"

"github.com/google/uuid"
Expand Down Expand Up @@ -84,6 +88,17 @@ func (o *OIDCProvider) callbackEndpoint(rw http.ResponseWriter, req *http.Reques
Int("nonce", nonce).
Msg("parsed user data")

switch client := session.Ar.GetClient().(type) {
case *DistrustClient:
log.Debug().Str("client", client.GetID()).Msg("distrust client found, performing additonal validation")
err := validateGroups(client, values)
if err != nil {
log.Warn().Err(err).Msg("group validation failed")
fmt.Fprintf(rw, "You are not allowed to access this application: %s", err.Error())
return
}
}

// since scopes do not work with discourse, we simply grant the openid scope
session.Ar.GrantScope("openid")

Expand Down Expand Up @@ -226,3 +241,25 @@ func (o *OIDCProvider) getAuthRoot(req *http.Request) string {
aroot := scheme + "://" + req.Host + o.root
return aroot
}

func validateGroups(client *DistrustClient, values url.Values) error {
userGroups := values.Get("groups")
groupMap := make(map[string]bool)
for _, g := range strings.Split(userGroups, ",") {
groupMap[g] = true
}
for _, allowed := range client.AllowGroups {
if groupMap[allowed] {
return nil
}
}
if len(client.AllowGroups) != 0 {
return errors.New("user is not in allowed groups for this client")
}
for _, denied := range client.DenyGroups {
if groupMap[denied] {
return errors.New("access is denied for user in group " + denied)
}
}
return nil
}
1 change: 1 addition & 0 deletions distrust.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ oidc:
clients:
test:
secret: foobar
allowGroups: ['team']
redirectURIs:
- 'https://openidconnect.net/callback'
22 changes: 16 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import (
type clientConfig struct {
Secret string
RedirectURIs []string
AllowGroups []string
DenyGroups []string
}

func main() {
Expand Down Expand Up @@ -102,12 +104,20 @@ func toFositeClients(clients map[string]clientConfig) map[string]fosite.Client {
hs, _ = bcrypt.GenerateFromPassword(hs, bcrypt.DefaultCost)
}

r[k] = &fosite.DefaultClient{
Secret: hs,
RedirectURIs: v.RedirectURIs,
ResponseTypes: []string{"id_token", "code", "token", "id_token token", "code id_token", "code token", "code id_token token"},
GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
Scopes: []string{"openid", "profile", "email"},
r[k] = &auth.DistrustClient{
DefaultClient: fosite.DefaultClient{
ID: k,
Secret: hs,
RedirectURIs: v.RedirectURIs,
ResponseTypes: []string{"id_token", "code", "token", "id_token token", "code id_token", "code token", "code id_token token"},
GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
Scopes: []string{"openid", "profile", "email"},
},
AllowGroups: v.AllowGroups,
DenyGroups: v.DenyGroups,
}
if len(v.AllowGroups) != 0 && len(v.DenyGroups) != 0 {
log.Warn().Str("client", k).Msg("allow and deny group options are set. allow groups will be used")
}
}
return r
Expand Down

0 comments on commit b80c3cd

Please sign in to comment.