lib/hydra/hydra_helper.go (144 lines of code) (raw):
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hydra
import (
"fmt"
"net/http"
"strings"
"google.golang.org/grpc/codes" /* copybara-comment */
"google.golang.org/grpc/status" /* copybara-comment */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/apis/hydraapi" /* copybara-comment: hydraapi */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/errutil" /* copybara-comment: errutil */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/ga4gh" /* copybara-comment: ga4gh */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/httputils" /* copybara-comment: httputils */
glog "github.com/golang/glog" /* copybara-comment */
)
const (
// StateIDKey uses to store stateID in hydra context.
StateIDKey = "state"
)
// ExtractLoginChallenge extracts login_challenge from request.
func ExtractLoginChallenge(r *http.Request) (string, *status.Status) {
n := httputils.QueryParam(r, "login_challenge")
if len(n) > 0 {
return n, nil
}
return "", httputils.NewInfoStatus(codes.InvalidArgument, "", "request must include query 'login challenge'")
}
// ExtractConsentChallenge extracts consent_challenge from request.
func ExtractConsentChallenge(r *http.Request) (string, *status.Status) {
n := httputils.QueryParam(r, "consent_challenge")
if len(n) > 0 {
return n, nil
}
return "", httputils.NewInfoStatus(codes.InvalidArgument, "", "request must include query 'consent_challenge'")
}
// ExtractStateIDInConsent extracts stateID in ConsentRequest Context.
func ExtractStateIDInConsent(consent *hydraapi.ConsentRequest) (string, *status.Status) {
st, ok := consent.Context[StateIDKey]
if !ok {
return "", nil
}
stateID, ok := st.(string)
if !ok {
return "", httputils.NewInfoStatus(codes.Internal, "", fmt.Sprintf("consent.Context[%s] in wrong type", StateIDKey))
}
return stateID, nil
}
// ExtractTokenIDInIntrospect finds token id in introspect result, return error if not found or in wront type.
func ExtractTokenIDInIntrospect(in *hydraapi.Introspection) (string, error) {
v, ok := in.Extra["tid"]
if !ok {
return "", status.Error(codes.Internal, "no tid in token")
}
tid, ok := v.(string)
if !ok {
return "", status.Error(codes.Internal, "tid has wrong type, expected string")
}
return tid, nil
}
// ExtractTokenIDInConsentSession finds token id in consent session.
func ExtractTokenIDInConsentSession(session *hydraapi.PreviousConsentSession) (string, error) {
if session == nil || session.Session == nil || session.Session.AccessToken == nil {
return "", status.Error(codes.Internal, "session.AccessToken does not exist exists")
}
v, ok := session.Session.AccessToken["tid"]
if !ok {
return "", status.Error(codes.Internal, "no tid in session")
}
tid, ok := v.(string)
if !ok {
return "", status.Error(codes.Internal, "tid has wrong type, expected string")
}
return tid, nil
}
// SendLoginSuccess sends login success to hydra.
func SendLoginSuccess(w http.ResponseWriter, r *http.Request, client *http.Client, hydraAdminURL, challenge, subject, stateID string, extra map[string]interface{}) {
addr, err := LoginSuccess(r, client, hydraAdminURL, challenge, subject, stateID, extra)
if err != nil {
httputils.WriteError(w, err)
return
}
httputils.WriteRedirect(w, r, addr)
}
// LoginSuccess is the redirect for successful login.
func LoginSuccess(r *http.Request, client *http.Client, hydraAdminURL, challenge, subject, stateID string, extra map[string]interface{}) (string, error) {
req := &hydraapi.HandledLoginRequest{
Subject: &subject,
Context: map[string]interface{}{},
}
if len(stateID) > 0 {
req.Context[StateIDKey] = stateID
}
for k, v := range extra {
req.Context[k] = v
}
resp, err := AcceptLogin(client, hydraAdminURL, challenge, req)
if err != nil {
return "", err
}
return resp.RedirectTo, nil
}
// SendLoginReject to hydra, requires a status err.
func SendLoginReject(w http.ResponseWriter, r *http.Request, client *http.Client, hydraAdminURL, challenge string, err error) {
resp, er := RejectLogin(client, hydraAdminURL, challenge, toRequestDeniedError(err))
if er != nil {
httputils.WriteError(w, err)
return
}
httputils.WriteRedirect(w, r, resp.RedirectTo)
}
// SendConsentReject to hydra, requires a status err.
func SendConsentReject(w http.ResponseWriter, r *http.Request, client *http.Client, hydraAdminURL, challenge string, err error) {
resp, er := RejectConsent(client, hydraAdminURL, challenge, toRequestDeniedError(err))
if er != nil {
httputils.WriteError(w, err)
return
}
httputils.WriteRedirect(w, r, resp.RedirectTo)
}
func toRequestDeniedError(err error) *hydraapi.RequestDeniedError {
name := errutil.ErrorReason(err)
if len(name) == 0 {
name = err.Error()
}
return &hydraapi.RequestDeniedError{
Code: int64(httputils.FromError(err)),
Description: err.Error(),
Name: name,
}
}
// NormalizeIdentity converts hydra special format in access token to ga4gh.Identity
// 1. move "scp" to "scope"
// 2. move "extra.identities" to "identities"
func NormalizeIdentity(id *ga4gh.Identity) *ga4gh.Identity {
if len(id.Scope) == 0 && len(id.Scp) > 0 {
id.Scope = strings.Join(id.Scp, " ")
}
// move "identities" claim in "ext" claim to top level identities claim.
l, ok := id.Extra["identities"]
if !ok {
return id
}
list, ok := l.([]interface{})
if !ok {
glog.Warning("id.Extra[identities] in wrong type")
return id
}
if id.Identities == nil {
id.Identities = map[string][]string{}
}
for i, it := range list {
identity, ok := it.(string)
if !ok {
glog.Warningf("id.Extra[identities][%d] in wrong type", i)
continue
}
id.Identities[identity] = []string{"DAM", "IC"}
}
return id
}