mirror of
https://github.com/eolinker/apinto
synced 2025-10-05 16:57:03 +08:00
156 lines
4.0 KiB
Go
156 lines
4.0 KiB
Go
package oauth2_introspection
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/eolinker/eosc"
|
|
http_service "github.com/eolinker/eosc/eocontext/http-context"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type IntrospectionResponseBody struct {
|
|
Active bool `json:"active"`
|
|
ClientId string `json:"client_id"`
|
|
Username string `json:"username"`
|
|
Scope string `json:"scope"`
|
|
Sub string `json:"sub"`
|
|
Aud string `json:"aud"`
|
|
Iss string `json:"iss"`
|
|
Exp int64 `json:"exp"`
|
|
Iat int64 `json:"iat"`
|
|
Nbf int64 `json:"nbf"`
|
|
Jti string `json:"jti"`
|
|
}
|
|
|
|
func setAppLabel(ctx http_service.IHttpContext, t *IntrospectionResponseBody, consumerBy string, allowAnonymous bool) error {
|
|
consumer := t.ClientId
|
|
switch consumerBy {
|
|
case "client_id":
|
|
case "username":
|
|
consumer = t.Username
|
|
default:
|
|
return fmt.Errorf("invalid consumer_by")
|
|
}
|
|
a, has := appManager.GetApp(consumer)
|
|
if !has {
|
|
if !allowAnonymous {
|
|
return fmt.Errorf("consumer(%s) not found", consumer)
|
|
}
|
|
a = appManager.AnonymousApp()
|
|
if a == nil {
|
|
return fmt.Errorf("anonymous app not found")
|
|
}
|
|
ctx.Proxy().Header().SetHeader("X-Consumer-Anonymous", "true")
|
|
}
|
|
ctx.SetLabel("application_id", a.Id())
|
|
ctx.SetLabel("application_name", a.Name())
|
|
ctx.Proxy().Header().SetHeader("X-Consumer-ID", a.Id())
|
|
ctx.Proxy().Header().SetHeader("X-Consumer-Username", a.Name())
|
|
|
|
return nil
|
|
}
|
|
|
|
func verifyIntrospection(t *IntrospectionResponseBody, clientId string, scopes map[string]struct{}) error {
|
|
if t.Active != true {
|
|
return fmt.Errorf("token is not active")
|
|
}
|
|
if t.ClientId != clientId {
|
|
return fmt.Errorf("invalid client_id")
|
|
}
|
|
|
|
now := time.Now()
|
|
if t.Exp < now.Unix() {
|
|
return fmt.Errorf("token is expired")
|
|
}
|
|
if t.Iat > now.Unix() {
|
|
return fmt.Errorf("token is not yet active")
|
|
}
|
|
if len(scopes) > 0 {
|
|
if _, ok := scopes[t.Scope]; !ok {
|
|
return fmt.Errorf("invalid scope")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkActive(t *IntrospectionResponseBody) bool {
|
|
if t.Active != true {
|
|
return false
|
|
}
|
|
now := time.Now()
|
|
if t.Exp < now.Unix() {
|
|
return false
|
|
}
|
|
if t.Iat > now.Unix() {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func doIntrospectAccessToken(client *http.Client, endpoint string, clientId string, clientSecret string, token string) (*eosc.Base[IntrospectionResponseBody], error) {
|
|
body := url.Values{}
|
|
body.Set("token", token)
|
|
body.Set("client_id", clientId)
|
|
body.Set("client_secret", clientSecret)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, endpoint, strings.NewReader(body.Encode()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
data, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("unexpected status code: %d, body: %s", resp.StatusCode, string(data))
|
|
}
|
|
t := new(eosc.Base[IntrospectionResponseBody])
|
|
err = json.Unmarshal(data, t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func retrieveAccessToken(ctx http_service.IHttpContext, tokenPosition string, tokenName string) string {
|
|
token := ""
|
|
switch tokenPosition {
|
|
case positionHeader:
|
|
token = ctx.Request().Header().GetHeader(tokenName)
|
|
return strings.TrimPrefix(token, "Bearer ")
|
|
case positionQuery:
|
|
token = ctx.Request().URI().GetQuery(tokenName)
|
|
case positionBody:
|
|
if strings.Contains(ctx.Request().ContentType(), "application/x-www-form-urlencoded") || strings.Contains(ctx.Request().ContentType(), "multipart/form-data") {
|
|
token = ctx.Request().Body().GetForm(tokenName)
|
|
} else if strings.Contains(ctx.Request().ContentType(), "application/json") {
|
|
body, _ := ctx.Request().Body().RawBody()
|
|
if string(body) != "" {
|
|
m := make(map[string]interface{})
|
|
err := json.Unmarshal(body, &m)
|
|
if err == nil {
|
|
if v, ok := m[tokenName]; ok {
|
|
token = fmt.Sprintf("%v", v)
|
|
}
|
|
} else {
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
default:
|
|
return ""
|
|
}
|
|
return strings.TrimPrefix(token, "Bearer ")
|
|
}
|