dashboard: improve display of device credentials, allow deletion (#2829)

* dashboard: improve display of device credentials, allow deletion

* fix test
This commit is contained in:
Caleb Doxsey 2021-12-20 12:19:54 -07:00 committed by GitHub
parent c064bc8e0e
commit 838c9e3a3d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 225 additions and 36 deletions

View file

@ -29,7 +29,6 @@ import (
"github.com/pomerium/pomerium/internal/telemetry/trace"
"github.com/pomerium/pomerium/internal/urlutil"
"github.com/pomerium/pomerium/pkg/cryptutil"
"github.com/pomerium/pomerium/pkg/grpc/device"
"github.com/pomerium/pomerium/pkg/grpc/directory"
"github.com/pomerium/pomerium/pkg/grpc/session"
"github.com/pomerium/pomerium/pkg/grpc/user"
@ -490,24 +489,40 @@ func (a *Authenticate) userInfo(w http.ResponseWriter, r *http.Request) error {
return fmt.Errorf("invalid webauthn url: %w", err)
}
var deviceCredentials []*device.Credential
type DeviceCredentialInfo struct {
ID string
}
var currentDeviceCredentials, otherDeviceCredentials []DeviceCredentialInfo
for _, id := range pbUser.GetDeviceCredentialIds() {
deviceCredentials = append(deviceCredentials, &device.Credential{
Id: id,
})
selected := false
for _, c := range pbSession.GetDeviceCredentials() {
if c.GetId() == id {
selected = true
}
}
if selected {
currentDeviceCredentials = append(currentDeviceCredentials, DeviceCredentialInfo{
ID: id,
})
} else {
otherDeviceCredentials = append(otherDeviceCredentials, DeviceCredentialInfo{
ID: id,
})
}
}
input := map[string]interface{}{
"IsImpersonated": isImpersonated,
"State": s, // local session state (cookie, header, etc)
"Session": pbSession, // current access, refresh, id token
"User": pbUser, // user details inferred from oidc id_token
"DeviceCredentials": deviceCredentials,
"DirectoryUser": pbDirectoryUser, // user details inferred from idp directory
"DirectoryGroups": groups, // user's groups inferred from idp directory
"csrfField": csrf.TemplateField(r),
"SignOutURL": signoutURL,
"WebAuthnURL": webAuthnURL,
"IsImpersonated": isImpersonated,
"State": s, // local session state (cookie, header, etc)
"Session": pbSession, // current access, refresh, id token
"User": pbUser, // user details inferred from oidc id_token
"CurrentDeviceCredentials": currentDeviceCredentials,
"OtherDeviceCredentials": otherDeviceCredentials,
"DirectoryUser": pbDirectoryUser, // user details inferred from idp directory
"DirectoryGroups": groups, // user's groups inferred from idp directory
"csrfField": csrf.TemplateField(r),
"SignOutURL": signoutURL,
"WebAuthnURL": webAuthnURL,
}
return a.templates.ExecuteTemplate(w, "userInfo.html", input)
}

View file

@ -0,0 +1,32 @@
package webauthn
import "github.com/pomerium/pomerium/pkg/grpc/session"
func containsString(elements []string, value string) bool {
for _, element := range elements {
if element == value {
return true
}
}
return false
}
func removeString(elements []string, value string) []string {
dup := make([]string, 0, len(elements))
for _, element := range elements {
if element != value {
dup = append(dup, element)
}
}
return dup
}
func removeSessionDeviceCredential(elements []*session.Session_DeviceCredential, id string) []*session.Session_DeviceCredential {
dup := make([]*session.Session_DeviceCredential, 0, len(elements))
for _, element := range elements {
if element.GetId() != id {
dup = append(dup, element)
}
}
return dup
}

View file

@ -37,8 +37,14 @@ import (
const maxAuthenticateResponses = 5
var (
errMissingDeviceType = httputil.NewError(http.StatusBadRequest, errors.New("device_type is a required parameter"))
errMissingRedirectURI = httputil.NewError(http.StatusBadRequest, errors.New("pomerium_redirect_uri is a required parameter"))
errMissingDeviceCredentialID = httputil.NewError(http.StatusBadRequest, errors.New(
urlutil.QueryDeviceCredentialID+" is a required parameter"))
errMissingDeviceType = httputil.NewError(http.StatusBadRequest, errors.New(
urlutil.QueryDeviceType+" is a required parameter"))
errMissingRedirectURI = httputil.NewError(http.StatusBadRequest, errors.New(
urlutil.QueryRedirectURI+" is a required parameter"))
errInvalidDeviceCredential = httputil.NewError(http.StatusBadRequest, errors.New(
"invalid device credential"))
)
// State is the state needed by the Handler to handle requests.
@ -91,6 +97,8 @@ func (h *Handler) handle(w http.ResponseWriter, r *http.Request) error {
return h.handleAuthenticate(w, r, s)
case r.FormValue("action") == "register":
return h.handleRegister(w, r, s)
case r.FormValue("action") == "unregister":
return h.handleUnregister(w, r, s)
}
return httputil.NewError(http.StatusNotFound, errors.New(http.StatusText(http.StatusNotFound)))
@ -297,6 +305,49 @@ func (h *Handler) handleRegister(w http.ResponseWriter, r *http.Request, state *
return h.saveSessionAndRedirect(w, r, state, redirectURIParam)
}
func (h *Handler) handleUnregister(w http.ResponseWriter, r *http.Request, state *State) error {
ctx := r.Context()
// get the user information
u, err := user.Get(ctx, state.Client, state.Session.GetUserId())
if err != nil {
return err
}
deviceCredentialID := r.FormValue(urlutil.QueryDeviceCredentialID)
if deviceCredentialID == "" {
return errMissingDeviceCredentialID
}
// ensure we only allow removing a device credential the user owns
if !containsString(u.GetDeviceCredentialIds(), deviceCredentialID) {
return errInvalidDeviceCredential
}
// delete the credential
deviceCredential, err := device.DeleteCredential(ctx, state.Client, deviceCredentialID)
if err != nil {
return err
}
// delete the corresponding enrollment
_, err = device.DeleteEnrollment(ctx, state.Client, deviceCredential.GetEnrollmentId())
if err != nil {
return err
}
// remove the credential from the user
u.DeviceCredentialIds = removeString(u.DeviceCredentialIds, deviceCredentialID)
_, err = user.Put(ctx, state.Client, u)
if err != nil {
return err
}
// remove the credential from the session
state.Session.DeviceCredentials = removeSessionDeviceCredential(state.Session.DeviceCredentials, deviceCredentialID)
return h.saveSessionAndRedirect(w, r, state, "/.pomerium")
}
func (h *Handler) handleView(w http.ResponseWriter, r *http.Request, state *State) error {
ctx := r.Context()