2020-03-07 13:06:57 +02:00
package auth
import (
2020-04-06 17:10:08 +02:00
"context"
2020-03-17 22:04:16 +02:00
"fmt"
2020-04-02 18:01:06 +02:00
"net"
2020-03-07 13:06:57 +02:00
"net/http"
2020-03-17 22:04:16 +02:00
"net/url"
2020-03-07 13:06:57 +02:00
"strings"
2020-04-02 18:44:48 +02:00
"github.com/micro/go-micro/v2/api/resolver"
2020-04-03 11:08:39 +02:00
"github.com/micro/go-micro/v2/api/resolver/path"
2020-03-07 13:06:57 +02:00
"github.com/micro/go-micro/v2/auth"
2020-04-02 18:01:06 +02:00
"github.com/micro/go-micro/v2/logger"
2020-04-07 11:28:39 +02:00
"golang.org/x/net/publicsuffix"
2020-03-07 13:06:57 +02:00
)
// CombinedAuthHandler wraps a server and authenticates requests
2020-04-07 10:40:40 +02:00
func CombinedAuthHandler ( prefix , namespace string , r resolver . Resolver , h http . Handler ) http . Handler {
2020-04-03 11:08:39 +02:00
if r == nil {
r = path . NewResolver ( )
}
2020-03-07 13:06:57 +02:00
return authHandler {
2020-04-07 10:40:40 +02:00
handler : h ,
resolver : r ,
auth : auth . DefaultAuth ,
servicePrefix : prefix ,
namespace : namespace ,
2020-03-07 13:06:57 +02:00
}
}
type authHandler struct {
2020-04-07 10:40:40 +02:00
handler http . Handler
auth auth . Auth
resolver resolver . Resolver
namespace string
servicePrefix string
2020-03-07 13:06:57 +02:00
}
func ( h authHandler ) ServeHTTP ( w http . ResponseWriter , req * http . Request ) {
2020-04-07 10:40:40 +02:00
// Determine the namespace and set it in the header
2020-04-07 11:28:39 +02:00
namespace := h . NamespaceFromRequest ( req )
2020-04-02 18:01:06 +02:00
req . Header . Set ( auth . NamespaceKey , namespace )
2020-03-17 21:24:10 +02:00
// Extract the token from the request
var token string
if header := req . Header . Get ( "Authorization" ) ; len ( header ) > 0 {
// Extract the auth token from the request
2020-03-25 13:20:53 +02:00
if strings . HasPrefix ( header , auth . BearerScheme ) {
token = header [ len ( auth . BearerScheme ) : ]
2020-03-17 21:24:10 +02:00
}
} else {
// Get the token out the cookies if not provided in headers
if c , err := req . Cookie ( "micro-token" ) ; err == nil && c != nil {
2020-03-23 18:19:30 +02:00
token = strings . TrimPrefix ( c . Value , auth . TokenCookieName + "=" )
2020-03-25 13:20:53 +02:00
req . Header . Set ( "Authorization" , auth . BearerScheme + token )
2020-03-17 21:24:10 +02:00
}
}
2020-03-07 13:06:57 +02:00
2020-03-23 18:19:30 +02:00
// Get the account using the token, fallback to a blank account
// since some endpoints can be unauthenticated, so the lack of an
// account doesn't necesserially mean a forbidden request
acc , err := h . auth . Inspect ( token )
if err != nil {
2020-04-02 18:01:06 +02:00
acc = & auth . Account { Namespace : namespace }
}
// Check the accounts namespace matches the namespace we're operating
// within. If not forbid the request and log the occurance.
if acc . Namespace != namespace {
2020-04-07 11:54:27 +02:00
logger . Debugf ( "Cross namespace request warning: account %v (%v) requested access to %v in the %v namespace" , acc . ID , acc . Namespace , req . URL . Path , namespace )
// http.Error(w, "Forbidden namespace", 403)
2020-03-07 13:06:57 +02:00
}
2020-04-02 18:44:48 +02:00
// Determine the name of the service being requested
endpoint , err := h . resolver . Resolve ( req )
2020-04-03 10:18:30 +02:00
if err == resolver . ErrInvalidPath || err == resolver . ErrNotFound {
// a file not served by the resolver has been requested (e.g. favicon.ico)
endpoint = & resolver . Endpoint { Path : req . URL . Path }
} else if err != nil {
2020-04-06 17:01:42 +02:00
logger . Error ( err )
2020-04-07 01:19:49 +02:00
http . Error ( w , err . Error ( ) , 500 )
2020-04-02 18:44:48 +02:00
return
2020-04-07 01:19:49 +02:00
} else {
2020-04-06 17:01:42 +02:00
// set the endpoint in the context so it can be used to resolve
// the request later
2020-04-06 17:10:08 +02:00
ctx := context . WithValue ( req . Context ( ) , resolver . Endpoint { } , endpoint )
2020-04-07 01:29:35 +02:00
* req = * req . Clone ( ctx )
2020-04-02 18:44:48 +02:00
}
2020-04-03 10:18:30 +02:00
// construct the resource name, e.g. home => go.micro.web.home
2020-04-07 10:40:40 +02:00
resName := h . servicePrefix
2020-04-03 10:18:30 +02:00
if len ( endpoint . Name ) > 0 {
resName = resName + "." + endpoint . Name
}
2020-04-02 18:44:48 +02:00
2020-04-03 10:45:39 +02:00
// determine the resource path. there is an inconsistency in how resolvers
// use method, some use it as Users.ReadUser (the rpc method), and others
// use it as the HTTP method, e.g GET. TODO: Refactor this to make it consistent.
resEndpoint := endpoint . Path
if len ( endpoint . Path ) == 0 {
resEndpoint = endpoint . Method
}
2020-04-02 18:44:48 +02:00
// Perform the verification check to see if the account has access to
// the resource they're requesting
2020-04-03 15:19:03 +02:00
res := & auth . Resource { Type : "service" , Name : resName , Endpoint : resEndpoint , Namespace : namespace }
2020-04-03 10:45:39 +02:00
if err := h . auth . Verify ( acc , res ) ; err == nil {
// The account has the necessary permissions to access the resource
2020-03-23 18:19:30 +02:00
h . handler . ServeHTTP ( w , req )
return
2020-03-07 13:06:57 +02:00
}
2020-04-02 18:01:06 +02:00
// The account is set, but they don't have enough permissions, hence
// we return a forbidden error.
2020-03-23 18:19:30 +02:00
if len ( acc . ID ) > 0 {
2020-04-07 01:19:49 +02:00
http . Error ( w , "Forbidden request" , 403 )
2020-03-07 13:06:57 +02:00
return
}
// If there is no auth login url set, 401
2020-03-23 18:19:30 +02:00
loginURL := h . auth . Options ( ) . LoginURL
2020-03-07 13:06:57 +02:00
if loginURL == "" {
2020-04-07 01:19:49 +02:00
http . Error ( w , "unauthorized request" , 401 )
2020-03-16 12:30:56 +02:00
return
2020-03-07 13:06:57 +02:00
}
// Redirect to the login path
2020-03-17 22:04:16 +02:00
params := url . Values { "redirect_to" : { req . URL . Path } }
loginWithRedirect := fmt . Sprintf ( "%v?%v" , loginURL , params . Encode ( ) )
http . Redirect ( w , req , loginWithRedirect , http . StatusTemporaryRedirect )
2020-03-07 13:06:57 +02:00
}
2020-04-02 18:01:06 +02:00
2020-04-07 11:28:39 +02:00
func ( h authHandler ) NamespaceFromRequest ( req * http . Request ) string {
2020-04-07 10:40:40 +02:00
// check to see what the provided namespace is, we only do
// domain mapping if the namespace is set to 'domain'
if h . namespace != "domain" {
return h . namespace
}
2020-04-06 17:01:42 +02:00
2020-04-03 15:09:25 +02:00
// determine the host, e.g. dev.micro.mu:8080
2020-04-07 10:40:40 +02:00
var host string
if h , _ , err := net . SplitHostPort ( req . Host ) ; err == nil {
host = h // host does contain a port
2020-04-07 11:08:06 +02:00
} else if strings . Contains ( err . Error ( ) , "missing port in address" ) {
2020-04-07 10:40:40 +02:00
host = req . Host // host does not contain a port
}
2020-04-02 18:01:06 +02:00
// check for an ip address
2020-04-03 15:09:25 +02:00
if net . ParseIP ( host ) != nil {
2020-04-07 10:40:40 +02:00
return auth . DefaultNamespace
2020-04-02 18:01:06 +02:00
}
// check for dev enviroment
if host == "localhost" || host == "127.0.0.1" {
2020-04-07 10:40:40 +02:00
return auth . DefaultNamespace
2020-04-02 18:01:06 +02:00
}
2020-04-07 11:28:39 +02:00
// extract the top level domain plus one (e.g. 'myapp.com')
domain , err := publicsuffix . EffectiveTLDPlusOne ( host )
if err != nil {
logger . Debugf ( "Unable to extract domain from %v" , host )
2020-04-07 10:40:40 +02:00
return auth . DefaultNamespace
2020-04-02 18:01:06 +02:00
}
2020-04-07 11:35:57 +02:00
// check to see if the domain matches the host of micro.mu, in
// these cases we return the default namespace
2020-04-07 11:34:26 +02:00
if domain == host || domain == "micro.mu" {
2020-04-07 11:28:39 +02:00
return auth . DefaultNamespace
}
// remove the domain from the host, leaving the subdomain
subdomain := strings . TrimSuffix ( host , "." + domain )
2020-04-07 10:40:40 +02:00
// return the reversed subdomain as the namespace
2020-04-07 11:28:39 +02:00
comps := strings . Split ( subdomain , "." )
for i := len ( comps ) / 2 - 1 ; i >= 0 ; i -- {
opp := len ( comps ) - 1 - i
comps [ i ] , comps [ opp ] = comps [ opp ] , comps [ i ]
2020-04-02 18:01:06 +02:00
}
2020-04-07 11:28:39 +02:00
return strings . Join ( comps , "." )
2020-04-02 18:01:06 +02:00
}