2020-03-07 14:06:57 +03:00
package auth
import (
2020-03-17 23:04:16 +03:00
"fmt"
2020-04-02 19:01:06 +03:00
"net"
2020-03-07 14:06:57 +03:00
"net/http"
2020-03-17 23:04:16 +03:00
"net/url"
2020-03-07 14:06:57 +03:00
"strings"
"github.com/micro/go-micro/v2/auth"
2020-04-02 19:01:06 +03:00
"github.com/micro/go-micro/v2/logger"
2020-03-07 14:06:57 +03:00
)
// CombinedAuthHandler wraps a server and authenticates requests
func CombinedAuthHandler ( h http . Handler ) http . Handler {
return authHandler {
2020-04-02 20:03:21 +03:00
handler : h ,
auth : auth . DefaultAuth ,
2020-03-07 14:06:57 +03:00
}
}
type authHandler struct {
2020-04-02 20:03:57 +03:00
handler http . Handler
auth auth . Auth
2020-03-07 14:06:57 +03:00
}
func ( h authHandler ) ServeHTTP ( w http . ResponseWriter , req * http . Request ) {
2020-04-02 19:01:06 +03:00
// Determine the namespace
namespace , err := namespaceFromRequest ( req )
if err != nil {
2020-04-03 15:29:48 +03:00
logger . Error ( err )
namespace = auth . DefaultNamespace
2020-04-02 19:01:06 +03:00
}
// Set the namespace in the header
req . Header . Set ( auth . NamespaceKey , namespace )
2020-03-17 22:24:10 +03: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 14:20:53 +03:00
if strings . HasPrefix ( header , auth . BearerScheme ) {
token = header [ len ( auth . BearerScheme ) : ]
2020-03-17 22:24:10 +03: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 19:19:30 +03:00
token = strings . TrimPrefix ( c . Value , auth . TokenCookieName + "=" )
2020-03-25 14:20:53 +03:00
req . Header . Set ( "Authorization" , auth . BearerScheme + token )
2020-03-17 22:24:10 +03:00
}
}
2020-03-07 14:06:57 +03:00
2020-03-23 19:19:30 +03: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 19:01:06 +03: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 {
logger . Warnf ( "Cross namespace request forbidden: account %v (%v) requested access to %v in the %v namespace" , acc . ID , acc . Namespace , req . URL . Path , namespace )
w . WriteHeader ( http . StatusForbidden )
2020-03-07 14:06:57 +03:00
}
2020-04-02 19:01:06 +03:00
// Perform the verification check to see if the account has access to
// the resource they're requesting
2020-03-23 19:19:30 +03:00
err = h . auth . Verify ( acc , & auth . Resource {
2020-04-02 19:01:06 +03:00
Type : "service" ,
Name : "go.micro.web" ,
Endpoint : req . URL . Path ,
Namespace : namespace ,
2020-03-23 19:19:30 +03:00
} )
2020-03-17 19:03:49 +03:00
2020-04-02 19:01:06 +03:00
// The account has the necessary permissions to access the resource
2020-03-23 19:19:30 +03:00
if err == nil {
h . handler . ServeHTTP ( w , req )
return
2020-03-07 14:06:57 +03:00
}
2020-04-02 19:01:06 +03:00
// The account is set, but they don't have enough permissions, hence
// we return a forbidden error.
2020-03-23 19:19:30 +03:00
if len ( acc . ID ) > 0 {
w . WriteHeader ( http . StatusForbidden )
2020-03-07 14:06:57 +03:00
return
}
// If there is no auth login url set, 401
2020-03-23 19:19:30 +03:00
loginURL := h . auth . Options ( ) . LoginURL
2020-03-07 14:06:57 +03:00
if loginURL == "" {
2020-03-23 19:19:30 +03:00
w . WriteHeader ( http . StatusUnauthorized )
2020-03-16 13:30:56 +03:00
return
2020-03-07 14:06:57 +03:00
}
// Redirect to the login path
2020-03-17 23:04:16 +03: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 14:06:57 +03:00
}
2020-04-02 19:01:06 +03:00
func namespaceFromRequest ( req * http . Request ) ( string , error ) {
2020-04-03 16:09:25 +03:00
// determine the host, e.g. dev.micro.mu:8080
host := req . URL . Host
if len ( host ) == 0 {
host = req . Host
}
2020-04-02 19:01:06 +03:00
// check for an ip address
2020-04-03 16:09:25 +03:00
if net . ParseIP ( host ) != nil {
2020-04-02 19:01:06 +03:00
return auth . DefaultNamespace , nil
}
// split the host to remove the port
host , _ , err := net . SplitHostPort ( req . Host )
if err != nil {
return "" , err
}
// check for dev enviroment
if host == "localhost" || host == "127.0.0.1" {
return auth . DefaultNamespace , nil
}
// if host is not a subdomain, deturn default namespace
comps := strings . Split ( host , "." )
if len ( comps ) != 3 {
return auth . DefaultNamespace , nil
}
// check for the micro.mu domain
domain := fmt . Sprintf ( "%v.%v" , comps [ 1 ] , comps [ 2 ] )
if domain == "micro.mu" {
return auth . DefaultNamespace , nil
}
// return the subdomain as the host
return comps [ 0 ] , nil
}