-
Notifications
You must be signed in to change notification settings - Fork 203
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #149 from eolinker/feature/pre_router
Feature/pre router
- Loading branch information
Showing
32 changed files
with
1,622 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package oauth2 | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strconv" | ||
"sync" | ||
"time" | ||
|
||
scope_manager "github.com/eolinker/apinto/scope-manager" | ||
|
||
"github.com/eolinker/apinto/resources" | ||
http_context "github.com/eolinker/eosc/eocontext/http-context" | ||
) | ||
|
||
const ( | ||
ResponseTypeCode = "code" | ||
ResponseTypeToken = "token" | ||
) | ||
|
||
func NewAuthorizeHandler() *AuthorizeHandler { | ||
return &AuthorizeHandler{} | ||
} | ||
|
||
type AuthorizeHandler struct { | ||
cache scope_manager.IProxyOutput[resources.ICache] | ||
once sync.Once | ||
} | ||
|
||
func (a *AuthorizeHandler) Handle(ctx http_context.IHttpContext, client *Client, params url.Values) { | ||
responseType := params.Get("response_type") | ||
if responseType == "" || !((responseType == ResponseTypeCode && client.EnableAuthorizationCode) || (responseType == ResponseTypeToken && client.EnableImplicitGrant)) { | ||
ctx.Response().SetBody([]byte(fmt.Sprintf("unsupported response type: %s,client id is %s", responseType, client.ClientId))) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
|
||
scope := params.Get("scope") | ||
if scope == "" && client.MandatoryScope { | ||
ctx.Response().SetBody([]byte("scope is required, client id is " + client.ClientId)) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
matchScope := false | ||
for _, s := range client.Scopes { | ||
if s == scope { | ||
matchScope = true | ||
break | ||
} | ||
} | ||
if !matchScope { | ||
ctx.Response().SetBody([]byte("invalid scope, client id is " + client.ClientId)) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
|
||
redirectURI := params.Get("redirect_uri") | ||
if redirectURI == "" { | ||
ctx.Response().SetBody([]byte("redirect uri is required, client id is " + client.ClientId)) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
|
||
matchRedirectUri := false | ||
for _, uri := range client.RedirectUrls { | ||
if uri == redirectURI { | ||
matchRedirectUri = true | ||
break | ||
} | ||
} | ||
if !matchRedirectUri { | ||
ctx.Response().SetBody([]byte("invalid redirect uri, client id is " + client.ClientId)) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
uri, err := url.Parse(redirectURI) | ||
if err != nil { | ||
ctx.Response().SetBody([]byte("invalid redirect uri, client id is " + client.ClientId)) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
a.once.Do(func() { | ||
a.cache = scope_manager.Auto[resources.ICache]("", "redis") | ||
}) | ||
list := a.cache.List() | ||
if len(list) < 1 { | ||
ctx.Response().SetBody([]byte("redis cache is not available")) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
cache := list[0] | ||
query := url.Values{} | ||
switch responseType { | ||
case ResponseTypeCode: | ||
{ | ||
// 授权码模式 | ||
provisionKey := params.Get("provision_key") | ||
if provisionKey != client.ProvisionKey { | ||
ctx.Response().SetBody([]byte("invalid provision key, client id is " + client.ClientId)) | ||
ctx.Response().SetStatus(http.StatusForbidden, "forbidden") | ||
return | ||
} | ||
code := generateRandomString() | ||
redisKey := fmt.Sprintf("apinto:oauth2_codes:%s:%s", os.Getenv("cluster_id"), code) | ||
field := map[string]interface{}{ | ||
"code": code, | ||
"scope": scope, | ||
} | ||
_, err = cache.HMSetN(ctx.Context(), redisKey, field, 6*time.Minute).Result() | ||
if err != nil { | ||
ctx.Response().SetBody([]byte(fmt.Sprintf("(%s)redis HMSet %s error: %s", client.ClientId, redisKey, err.Error()))) | ||
ctx.Response().SetStatus(http.StatusInternalServerError, "server error") | ||
return | ||
} | ||
query.Set("code", code) | ||
} | ||
case ResponseTypeToken: | ||
{ | ||
token, err := generateToken(ctx.Context(), cache, client.ClientId, client.TokenExpiration, client.RefreshTokenTTL, scope, false) | ||
if err != nil { | ||
ctx.Response().SetBody([]byte(fmt.Sprintf("(%s)generate token error: %s", client.ClientId, err.Error()))) | ||
ctx.Response().SetStatus(http.StatusInternalServerError, "server error") | ||
return | ||
} | ||
query.Set("access_token", token.AccessToken) | ||
query.Set("token_type", "bearer") | ||
query.Set("expires_in", strconv.Itoa(token.ExpiresIn)) | ||
} | ||
} | ||
|
||
state := params.Get("state") | ||
if state != "" { | ||
query.Set("state", state) | ||
} | ||
data, _ := json.Marshal(map[string]interface{}{ | ||
"redirect_uri": fmt.Sprintf("%s?%s", uri.String(), query.Encode()), | ||
}) | ||
ctx.Response().SetBody(data) | ||
ctx.Response().SetStatus(http.StatusOK, "OK") | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package oauth2 | ||
|
||
import "github.com/eolinker/apinto/application" | ||
|
||
const ( | ||
GrantAuthorizationCode = "authorization_code" | ||
GrantClientCredentials = "client_credentials" | ||
GrantRefreshToken = "refresh_token" | ||
) | ||
|
||
type Config struct { | ||
application.Auth | ||
Users []*User `json:"users" label:"用户列表"` | ||
} | ||
|
||
type User struct { | ||
Pattern Pattern `json:"pattern" label:"用户信息"` | ||
application.User | ||
} | ||
|
||
type Pattern struct { | ||
ClientId string `json:"client_id"` | ||
ClientSecret string `json:"client_secret"` | ||
ClientType string `json:"client_type"` | ||
HashSecret bool `json:"hash_secret"` | ||
RedirectUrls []string `json:"redirect_urls" label:"重定向URL"` | ||
Scopes []string `json:"scopes" label:"授权范围"` | ||
MandatoryScope bool `json:"mandatory_scope" label:"强制授权"` | ||
ProvisionKey string `json:"provision_key" label:"Provision Key"` | ||
TokenExpiration int `json:"token_expiration" label:"令牌过期时间"` | ||
RefreshTokenTTL int `json:"refresh_token_ttl" label:"刷新令牌TTL"` | ||
EnableAuthorizationCode bool `json:"enable_authorization_code" label:"启用授权码模式"` | ||
EnableImplicitGrant bool `json:"enable_implicit_grant" label:"启用隐式授权模式"` | ||
EnableClientCredentials bool `json:"enable_client_credentials" label:"启用客户端凭证模式"` | ||
AcceptHttpIfAlreadyTerminated bool `json:"accept_http_if_already_terminated" label:"如果已终止,则接受HTTP"` | ||
ReuseRefreshToken bool `json:"reuse_refresh_token" label:"重用刷新令牌"` | ||
PersistentRefreshToken bool `json:"persistent_refresh_token" label:"持久刷新令牌"` | ||
} | ||
|
||
func (u *User) Username() string { | ||
return u.Pattern.ClientId | ||
} |
Oops, something went wrong.