This is an automated email from the ASF dual-hosted git repository.

tianxiaoliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-mesher.git


The following commit(s) were added to refs/heads/master by this push:
     new b89c769  add oauth2 authorization module (#91)
b89c769 is described below

commit b89c7696bdac0df8bab00b0b14c15ebe6285fd23
Author: Cong Young <[email protected]>
AuthorDate: Fri Dec 13 16:29:36 2019 +0800

    add oauth2 authorization module (#91)
---
 cmd/mesher/mesher.go                        |   2 +
 docs/oauth2/oauth2.md                       |  54 +++++++++++
 examples/edge/conf/chassis.yaml             |   2 +-
 go.mod                                      |   1 +
 proxy/handler/oauth2/api.go                 |  39 ++++++++
 proxy/handler/oauth2/oauth2_handler.go      | 137 ++++++++++++++++++++++++++++
 proxy/handler/oauth2/oauth2_handler_test.go | 117 ++++++++++++++++++++++++
 proxy/protocol/http/gateway.go              |   1 +
 8 files changed, 352 insertions(+), 1 deletion(-)

diff --git a/cmd/mesher/mesher.go b/cmd/mesher/mesher.go
index 17e9c31..d8c3369 100644
--- a/cmd/mesher/mesher.go
+++ b/cmd/mesher/mesher.go
@@ -39,6 +39,8 @@ import (
        _ "github.com/apache/servicecomb-mesher/proxy/pkg/egress/pilot"
 
        _ "github.com/apache/servicecomb-mesher/proxy/control/istio"
+
+       _ "github.com/apache/servicecomb-mesher/proxy/handler/oauth2"
 )
 
 func main() {
diff --git a/docs/oauth2/oauth2.md b/docs/oauth2/oauth2.md
new file mode 100644
index 0000000..0e82f02
--- /dev/null
+++ b/docs/oauth2/oauth2.md
@@ -0,0 +1,54 @@
+# OAuth2
+
+Mesher provides a high-level general-purpose middleware abstraction layer. One 
of the abstractions is oauth2, which is free  user learning [complexity inside 
handler 
chain](https://docs.go-chassis.com/dev-guides/how-to-implement-handler.html), 
so that users only need to focus on the development of their own business.
+
+## configuration
+
+Writing business code
+
+When you use authorization code model, you need to implement the follow 
parameters. Otherwise you need to implement the interface of config in 
oauth2/api.go.
+
+For example, implement the authorization code model **In oauth2_handler.go**
+
+```go
+    Use(&OAuth2{
+               GrantType: "authorization_code",       // Registration grand 
type
+                                                      // The default is the 
authorization code model
+               Authenticate: func(accessToken string, req *http.Request) error 
{
+            // implement the function 
+                       return nil
+               },
+               UseConfig: &oauth2.Config{
+                       ClientID:     "",                        // (required, 
string) your client_ID
+                       ClientSecret: "",                        // (required, 
string) your client_Secret
+                       Scopes:       []string{""},              // (optional, 
string) scope specifies requested permissions
+                       RedirectURL:  "",                        // (required, 
string) URL to redirect users going through the OAuth2 flow, 
+                       Endpoint: oauth2.Endpoint{               // (required, 
string) your auth server endpoint
+                               AuthURL:  "",
+                               TokenURL: "",
+                       },
+               },
+       })
+```
+
+Change the configuration file and add the oauth2 handler to the chain. Note 
that as authentication, generally speaking,it is a server function, it must be 
placed in the provider chain.
+
+```yaml
+handler:
+    chain:
+      Consumer:
+        outgoing: 
+      Provider:
+        incoming: oauth2 #provider handlers
+```
+
+## How to use
+
+**oauth2-handler Init**
+- [1] Implement the interface definition in /oauth2/api.go.
+- [2] Adding oauth2's provider handler name oauth2 defined in /oauth2 to 
providerChain.
+- [3] You must import proxy/handler/oauth2 to init oauth2 handler. All the 
handlers which are customized for mesher are defined in file 
cmd/mesher/mesher.go.
+- more details about handler chains in 
[go-chassis](https://github.com/go-chassis/go-chassis#readme)
+
+
+
diff --git a/examples/edge/conf/chassis.yaml b/examples/edge/conf/chassis.yaml
index de8325b..214bcf6 100644
--- a/examples/edge/conf/chassis.yaml
+++ b/examples/edge/conf/chassis.yaml
@@ -20,7 +20,7 @@ cse:
       Consumer:
         outgoing: 
router,bizkeeper-consumer,loadbalance,tracing-consumer,transport #consumer 
handlers
       Provider:
-        incoming: tracing-provider #provider handlers
+        incoming: oauth2,tracing-provider #provider handlers
 
 ## Mesher TLS is base on Go Chassis TLS config,  
https://docs.go-chassis.com/user-guides/tls.html
 ssl:
diff --git a/go.mod b/go.mod
index d21e1cb..00ab1a4 100644
--- a/go.mod
+++ b/go.mod
@@ -23,6 +23,7 @@ require (
        github.com/tetratelabs/go2sky v0.1.1-0.20190703154722-1eaab8035277
        github.com/urfave/cli v1.20.1-0.20181029213200-b67dcf995b6a
        golang.org/x/net v0.0.0-20190311183353-d8887717615a
+       golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
        golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
        google.golang.org/grpc v1.19.1
        gopkg.in/inf.v0 v0.9.1 // indirect
diff --git a/proxy/handler/oauth2/api.go b/proxy/handler/oauth2/api.go
new file mode 100644
index 0000000..7c774a3
--- /dev/null
+++ b/proxy/handler/oauth2/api.go
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 oauth2
+
+import (
+       "golang.org/x/oauth2"
+       "net/http"
+)
+
+var auth *OAuth2
+
+// OAuth2 should implement oauth2 server side logic
+// it is singleton
+type OAuth2 struct {
+       GrantType    string                                            // 
required
+       UseConfig    *oauth2.Config                                    // 
required
+       Authenticate func(accessToken string, req *http.Request) error // 
optional
+}
+
+// Use put a custom oauth2 logic
+// then register handler to chassis
+func Use(middleware *OAuth2) {
+       auth = middleware
+}
diff --git a/proxy/handler/oauth2/oauth2_handler.go 
b/proxy/handler/oauth2/oauth2_handler.go
new file mode 100644
index 0000000..ed50d1e
--- /dev/null
+++ b/proxy/handler/oauth2/oauth2_handler.go
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 oauth2
+
+import (
+       "context"
+       "errors"
+       "github.com/go-chassis/go-chassis/core/handler"
+       "github.com/go-chassis/go-chassis/core/invocation"
+       "github.com/go-mesh/openlogging"
+       "net/http"
+       "time"
+)
+
+// errors
+var (
+       ErrNoGrandType  = errors.New("no grant_type found")
+       ErrInvalidCode  = errors.New("invalid code")
+       ErrInvalidToken = errors.New("invalid authorization")
+       ErrInvalidAuth  = errors.New("invalid authentication")
+       ErrExpiredToken = errors.New("expired token")
+)
+
+// AuthName is a constant
+const AuthName = "oauth2"
+
+// Handler is is a oauth2 pre process raw data in handler
+type Handler struct {
+}
+
+// Handle is provider
+func (oa *Handler) Handle(chain *handler.Chain, inv *invocation.Invocation, cb 
invocation.ResponseCallBack) {
+       if req, ok := inv.Args.(*http.Request); ok {
+               grantType := req.FormValue("grant_type")
+               if grantType == "" {
+                       WriteBackErr(ErrNoGrandType, http.StatusUnauthorized, 
cb)
+                       return
+               }
+
+               if auth != nil && auth.GrantType == "authorization_code" {
+                       if req, ok := inv.Args.(*http.Request); ok {
+                               code := req.FormValue("code")
+                               if code == "" {
+                                       WriteBackErr(ErrInvalidCode, 
http.StatusUnauthorized, cb)
+                                       return
+                               }
+
+                               accessToken, err := getToken(code, cb)
+                               if err != nil {
+                                       openlogging.Error("authorization error: 
" + err.Error())
+                                       WriteBackErr(ErrInvalidToken, 
http.StatusUnauthorized, cb)
+                                       return
+                               }
+
+                               if auth.Authenticate != nil {
+                                       err = auth.Authenticate(accessToken, 
req)
+                                       if err != nil {
+                                               
openlogging.Error("authentication error: " + err.Error())
+                                               WriteBackErr(ErrInvalidAuth, 
http.StatusUnauthorized, cb)
+                                               return
+                                       }
+                               }
+                       }
+               }
+               chain.Next(inv, func(r *invocation.Response) error {
+                       return cb(r)
+               })
+       }
+}
+
+// getToken deal with the authorization code and return the token
+func getToken(code string, cb invocation.ResponseCallBack) (accessToken 
string, err error) {
+       if auth.UseConfig != nil {
+               config := auth.UseConfig
+               token, err := config.Exchange(context.Background(), code)
+               if err != nil {
+                       openlogging.Error("get token failed, errors: " + 
err.Error())
+                       WriteBackErr(ErrInvalidCode, http.StatusUnauthorized, 
cb)
+                       return "", err
+               }
+
+               // set the expiry token in 30 minutes
+               token.Expiry = time.Now().Add(30 * 60 * time.Second)
+               if time.Now().After(token.Expiry) {
+                       return "", ErrExpiredToken
+               }
+               accessToken = token.AccessToken
+               return accessToken, nil
+       }
+       return "", nil
+}
+
+// Name returns router string
+func (oa *Handler) Name() string {
+       return AuthName
+}
+
+// NewOAuth2 returns new auth handler
+func NewOAuth2() handler.Handler {
+       return &Handler{}
+}
+
+func init() {
+       err := handler.RegisterHandler(AuthName, NewOAuth2)
+       if err != nil {
+               openlogging.Error("register handler error: " + err.Error())
+               return
+       }
+}
+
+// WriteBackErr write err and callback
+func WriteBackErr(err error, status int, cb invocation.ResponseCallBack) {
+       r := &invocation.Response{
+               Err:    err,
+               Status: status,
+       }
+       err = cb(r)
+       if err != nil {
+               openlogging.Error("response error: " + err.Error())
+               return
+       }
+}
diff --git a/proxy/handler/oauth2/oauth2_handler_test.go 
b/proxy/handler/oauth2/oauth2_handler_test.go
new file mode 100644
index 0000000..ba5df52
--- /dev/null
+++ b/proxy/handler/oauth2/oauth2_handler_test.go
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 oauth2
+
+import (
+       "github.com/go-chassis/go-chassis/core/config"
+       "github.com/go-chassis/go-chassis/core/config/model"
+       "github.com/go-chassis/go-chassis/core/handler"
+       "github.com/go-chassis/go-chassis/core/invocation"
+       "github.com/stretchr/testify/assert"
+       "golang.org/x/oauth2"
+       "net/http"
+       "testing"
+)
+
+func initHandler() handler.Chain {
+       c := handler.Chain{}
+       c.AddHandler(&Handler{})
+       return c
+}
+
+func initInv() *invocation.Invocation {
+
+       config.GlobalDefinition = &model.GlobalCfg{}
+       config.GlobalDefinition.Cse.Handler.Chain.Provider = 
make(map[string]string)
+       config.GlobalDefinition.Cse.Handler.Chain.Provider["outgoing"] = 
AuthName
+
+       var i *invocation.Invocation
+
+       i = invocation.New(nil)
+       i.MicroServiceName = "service1"
+       i.SchemaID = "schema1"
+       i.OperationID = "SayHello"
+       i.Endpoint = ""
+
+       return i
+}
+func TestOAuth2_Handle(t *testing.T) {
+       c := initHandler()
+       i := initInv()
+
+       Use(&OAuth2{
+               GrantType: "authorization_code",
+               Authenticate: func(at string, req *http.Request) error {
+                       return nil
+               },
+               UseConfig: &oauth2.Config{
+                       ClientID:     "",           // (required, string) your 
client_ID
+                       ClientSecret: "",           // (required, string) your 
client_Secret
+                       Scopes:       []string{""}, // (optional, string) scope 
specifies requested permissions
+                       RedirectURL:  "",           // (required, string) URL 
to redirect users going through the OAuth2 flow
+                       Endpoint: oauth2.Endpoint{ // (required, string) your 
auth server endpoint
+                               AuthURL:  "",
+                               TokenURL: "",
+                       },
+               },
+       })
+
+       t.Run("Invalid grant_type", func(t *testing.T) {
+               req, err := http.NewRequest(http.MethodPost, 
"https://api/?grant_type=test&code=test";, nil)
+               if err != nil {
+                       t.Errorf("authorization failed: %s", err.Error())
+                       return
+               }
+               i.Args = req
+
+               i.SetHeader("Authorization", "Basic dGVzdDp0ZXN0")
+               c.Next(i, func(r *invocation.Response) error {
+                       assert.Error(t, r.Err)
+                       return r.Err
+               })
+       })
+
+       t.Run("Normal grant_type", func(t *testing.T) {
+               req, err := http.NewRequest(http.MethodPost, 
"https://api/?grant_type=authorization_code&code=test";, nil)
+               if err != nil {
+                       t.Errorf("authorization failed: %s", err.Error())
+                       return
+               }
+               i.Args = req
+
+               c.Next(i, func(r *invocation.Response) error {
+                       assert.NoError(t, r.Err)
+                       return r.Err
+               })
+       })
+
+       t.Run("Null grant_type", func(t *testing.T) {
+               req, err := http.NewRequest(http.MethodPost, 
"https://api/?grant_type=&code=test";, nil)
+               if err != nil {
+                       t.Errorf("authorization failed: %s", err.Error())
+                       return
+               }
+               i.Args = req
+
+               i.SetHeader("Authorization", "Basic dGVzdDp0ZXN0")
+               c.Next(i, func(r *invocation.Response) error {
+                       assert.NoError(t, r.Err)
+                       return r.Err
+               })
+       })
+}
diff --git a/proxy/protocol/http/gateway.go b/proxy/protocol/http/gateway.go
index ff8f173..d89dd4d 100644
--- a/proxy/protocol/http/gateway.go
+++ b/proxy/protocol/http/gateway.go
@@ -50,6 +50,7 @@ func HandleIngressTraffic(w http.ResponseWriter, r 
*http.Request) {
        inv := &invocation.Invocation{}
        inv.Reply = rest.NewResponse()
        inv.Protocol = "rest"
+       inv.Args = r
        h := make(map[string]string)
        for k := range r.Header {
                h[k] = r.Header.Get(k)

Reply via email to