*: add support for bearer authentication on API
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		@ -10,20 +10,20 @@ from structlog.stdlib import get_logger
 | 
				
			|||||||
from authentik.core.models import Token, TokenIntents, User
 | 
					from authentik.core.models import Token, TokenIntents, User
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LOGGER = get_logger()
 | 
					LOGGER = get_logger()
 | 
				
			||||||
X_AUTHENTIK_PREVENT_BASIC_HEADER = "HTTP_X_AUTHENTIK_PREVENT_BASIC"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def token_from_header(raw_header: bytes) -> Optional[Token]:
 | 
					def token_from_header(raw_header: bytes) -> Optional[Token]:
 | 
				
			||||||
    """raw_header in the Format of `Basic dGVzdDp0ZXN0`"""
 | 
					    """raw_header in the Format of `Basic dGVzdDp0ZXN0`"""
 | 
				
			||||||
    auth_credentials = raw_header.decode()
 | 
					    auth_credentials = raw_header.decode()
 | 
				
			||||||
    # Accept headers with Type format and without
 | 
					    # Accept headers with Type format and without
 | 
				
			||||||
    if " " in auth_credentials:
 | 
					    if " " not in auth_credentials:
 | 
				
			||||||
        auth_type, auth_credentials = auth_credentials.split()
 | 
					 | 
				
			||||||
        if auth_type.lower() != "basic":
 | 
					 | 
				
			||||||
            LOGGER.debug(
 | 
					 | 
				
			||||||
                "Unsupported authentication type, denying", type=auth_type.lower()
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					    auth_type, auth_credentials = auth_credentials.split()
 | 
				
			||||||
 | 
					    if auth_type.lower() not in ["basic", "bearer"]:
 | 
				
			||||||
 | 
					        LOGGER.debug("Unsupported authentication type, denying", type=auth_type.lower())
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					    password = auth_credentials
 | 
				
			||||||
 | 
					    if auth_type.lower() == "basic":
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            auth_credentials = b64decode(auth_credentials.encode()).decode()
 | 
					            auth_credentials = b64decode(auth_credentials.encode()).decode()
 | 
				
			||||||
        except (UnicodeDecodeError, Error):
 | 
					        except (UnicodeDecodeError, Error):
 | 
				
			||||||
@ -43,10 +43,10 @@ def token_from_header(raw_header: bytes) -> Optional[Token]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AuthentikTokenAuthentication(BaseAuthentication):
 | 
					class AuthentikTokenAuthentication(BaseAuthentication):
 | 
				
			||||||
    """Token-based authentication using HTTP Basic authentication"""
 | 
					    """Token-based authentication using HTTP Bearer authentication"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
 | 
					    def authenticate(self, request: Request) -> Union[tuple[User, Any], None]:
 | 
				
			||||||
        """Token-based authentication using HTTP Basic authentication"""
 | 
					        """Token-based authentication using HTTP Bearer authentication"""
 | 
				
			||||||
        auth = get_authorization_header(request)
 | 
					        auth = get_authorization_header(request)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        token = token_from_header(auth)
 | 
					        token = token_from_header(auth)
 | 
				
			||||||
@ -56,6 +56,4 @@ class AuthentikTokenAuthentication(BaseAuthentication):
 | 
				
			|||||||
        return (token.user, None)
 | 
					        return (token.user, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def authenticate_header(self, request: Request) -> str:
 | 
					    def authenticate_header(self, request: Request) -> str:
 | 
				
			||||||
        if X_AUTHENTIK_PREVENT_BASIC_HEADER in request._request.META:
 | 
					        return "Bearer"
 | 
				
			||||||
            return ""
 | 
					 | 
				
			||||||
        return 'Basic realm="authentik"'
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ from authentik.core.models import Token, TokenIntents
 | 
				
			|||||||
class TestAPIAuth(TestCase):
 | 
					class TestAPIAuth(TestCase):
 | 
				
			||||||
    """Test API Authentication"""
 | 
					    """Test API Authentication"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_valid(self):
 | 
					    def test_valid_basic(self):
 | 
				
			||||||
        """Test valid token"""
 | 
					        """Test valid token"""
 | 
				
			||||||
        token = Token.objects.create(
 | 
					        token = Token.objects.create(
 | 
				
			||||||
            intent=TokenIntents.INTENT_API, user=get_anonymous_user()
 | 
					            intent=TokenIntents.INTENT_API, user=get_anonymous_user()
 | 
				
			||||||
@ -19,6 +19,13 @@ class TestAPIAuth(TestCase):
 | 
				
			|||||||
        auth = b64encode(f":{token.key}".encode()).decode()
 | 
					        auth = b64encode(f":{token.key}".encode()).decode()
 | 
				
			||||||
        self.assertEqual(token_from_header(f"Basic {auth}".encode()), token)
 | 
					        self.assertEqual(token_from_header(f"Basic {auth}".encode()), token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_valid_bearer(self):
 | 
				
			||||||
 | 
					        """Test valid token"""
 | 
				
			||||||
 | 
					        token = Token.objects.create(
 | 
				
			||||||
 | 
					            intent=TokenIntents.INTENT_API, user=get_anonymous_user()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assertEqual(token_from_header(f"Bearer {token.key}".encode()), token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_invalid_type(self):
 | 
					    def test_invalid_type(self):
 | 
				
			||||||
        """Test invalid type"""
 | 
					        """Test invalid type"""
 | 
				
			||||||
        self.assertIsNone(token_from_header("foo bar".encode()))
 | 
					        self.assertIsNone(token_from_header("foo bar".encode()))
 | 
				
			||||||
 | 
				
			|||||||
@ -143,7 +143,7 @@ SWAGGER_SETTINGS = {
 | 
				
			|||||||
        "authentik.api.pagination_schema.PaginationInspector",
 | 
					        "authentik.api.pagination_schema.PaginationInspector",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "SECURITY_DEFINITIONS": {
 | 
					    "SECURITY_DEFINITIONS": {
 | 
				
			||||||
        "token": {"type": "apiKey", "name": "Authorization", "in": "header"}
 | 
					        "Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,6 @@ package ak
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"crypto/tls"
 | 
						"crypto/tls"
 | 
				
			||||||
	"encoding/base64"
 | 
					 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
@ -20,7 +19,7 @@ func (ac *APIController) initWS(pbURL url.URL, outpostUUID strfmt.UUID) {
 | 
				
			|||||||
	pathTemplate := "%s://%s/ws/outpost/%s/"
 | 
						pathTemplate := "%s://%s/ws/outpost/%s/"
 | 
				
			||||||
	scheme := strings.ReplaceAll(pbURL.Scheme, "http", "ws")
 | 
						scheme := strings.ReplaceAll(pbURL.Scheme, "http", "ws")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	authHeader := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("Basic :%s", ac.token)))
 | 
						authHeader := fmt.Sprintf("Bearer %s", ac.token)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	header := http.Header{
 | 
						header := http.Header{
 | 
				
			||||||
		"Authorization": []string{authHeader},
 | 
							"Authorization": []string{authHeader},
 | 
				
			||||||
 | 
				
			|||||||
@ -13,12 +13,12 @@ consumes:
 | 
				
			|||||||
produces:
 | 
					produces:
 | 
				
			||||||
  - application/json
 | 
					  - application/json
 | 
				
			||||||
securityDefinitions:
 | 
					securityDefinitions:
 | 
				
			||||||
  token:
 | 
					  Bearer:
 | 
				
			||||||
    type: apiKey
 | 
					    type: apiKey
 | 
				
			||||||
    name: Authorization
 | 
					    name: Authorization
 | 
				
			||||||
    in: header
 | 
					    in: header
 | 
				
			||||||
security:
 | 
					security:
 | 
				
			||||||
  - token: []
 | 
					  - Bearer: []
 | 
				
			||||||
paths:
 | 
					paths:
 | 
				
			||||||
  /admin/apps/:
 | 
					  /admin/apps/:
 | 
				
			||||||
    get:
 | 
					    get:
 | 
				
			||||||
 | 
				
			|||||||
@ -16,7 +16,6 @@ export const DEFAULT_CONFIG = new Configuration({
 | 
				
			|||||||
    basePath: "/api/v2beta",
 | 
					    basePath: "/api/v2beta",
 | 
				
			||||||
    headers: {
 | 
					    headers: {
 | 
				
			||||||
        "X-CSRFToken": getCookie("authentik_csrf"),
 | 
					        "X-CSRFToken": getCookie("authentik_csrf"),
 | 
				
			||||||
        "X-Authentik-Prevent-Basic": "true"
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    middleware: [
 | 
					    middleware: [
 | 
				
			||||||
        API_DRAWER_MIDDLEWARE,
 | 
					        API_DRAWER_MIDDLEWARE,
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user