-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8efc446
commit ed97150
Showing
9 changed files
with
381 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.godot/ | ||
**/.DS_Store |
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,19 @@ | ||
@tool | ||
extends EditorPlugin | ||
|
||
const AUTOLOAD_NAME = "Keycloak" | ||
|
||
func _enter_tree(): | ||
set_default("realm_id", "my_keycloak_realm") | ||
set_default("client_id", "game_client_id") | ||
set_default("client_secret", "super_secret") | ||
set_default("server_addr", "127.0.0.1") | ||
set_default("server_port", 8080) | ||
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/keycloak/src/auth.gd") | ||
|
||
func _exit_tree(): | ||
remove_autoload_singleton(AUTOLOAD_NAME) | ||
|
||
func set_default(key, value): | ||
if not ProjectSettings.has_setting(key): | ||
ProjectSettings.set_setting("keycloak/%s" % key, value) |
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,7 @@ | ||
[plugin] | ||
|
||
name="Keycloak" | ||
description="" | ||
author="Conrado Costa" | ||
version="0.1" | ||
script="keycloak.gd" |
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,211 @@ | ||
extends Node | ||
|
||
signal token_received | ||
signal authentication_failed(String) | ||
|
||
var public_key: CryptoKey | ||
|
||
var basic = load("res://addons/keycloak/src/auth_types/basic.gd") | ||
var google = load("res://addons/keycloak/src/auth_types/google.gd") | ||
|
||
var realm_id: String : | ||
get: | ||
return ProjectSettings.get_setting("keycloak/realm_id") | ||
var client_id: String : | ||
get: | ||
return ProjectSettings.get_setting("keycloak/client_id") | ||
var client_secret: String : | ||
get: | ||
return ProjectSettings.get_setting("keycloak/client_secret") | ||
var server_addr: String : | ||
get: | ||
return ProjectSettings.get_setting("keycloak/server_addr") | ||
var server_port: int : | ||
get: | ||
return ProjectSettings.get_setting("keycloak/server_port") | ||
var token_url: String : | ||
get: | ||
return "/realms/%s/protocol/openid-connect/token" % realm_id | ||
var auth_url: | ||
get: | ||
return "/realms/%s/protocol/openid-connect/auth" % realm_id | ||
|
||
# Used to receive access code using external auth providers (e.g. Google) | ||
const LOCAL_CLIENT_PORT := 30581 | ||
const LOCAL_CLIENT_ADDR := "127.0.0.1" | ||
|
||
var redirect_server := TCPServer.new() | ||
var redirect_uri: String = "http://%s:%s" % [LOCAL_CLIENT_ADDR, LOCAL_CLIENT_PORT] | ||
|
||
var _access_token: String | ||
var _refresh_token: String | ||
|
||
func is_token_valid(token: String) -> bool: | ||
if not public_key: | ||
await get_pub_key() | ||
|
||
var jwt_algorithm: JWTAlgorithm = JWTAlgorithmBuilder.RS256(public_key, public_key) | ||
var jwt_verifier: JWTVerifier = JWT.require(jwt_algorithm) \ | ||
.build(int(Time.get_unix_time_from_system() + 300)) | ||
|
||
if jwt_verifier.verify(token) == JWTVerifier.JWTExceptions.OK: | ||
return true | ||
else: | ||
push_error(jwt_verifier.exception) | ||
return false | ||
|
||
func get_token() -> String: | ||
var jwt_decoder = JWTDecoder.new(_access_token) | ||
|
||
if Time.get_unix_time_from_system() <= jwt_decoder.get_expires_at(): | ||
print("Token still good!") | ||
return _access_token | ||
else: | ||
print("Expired") | ||
return await refresh_token() | ||
|
||
func _process(_delta): | ||
if redirect_server.is_connection_available(): | ||
var connection = redirect_server.take_connection() | ||
var request = connection.get_string(connection.get_available_bytes()) | ||
if request: | ||
set_process(false) | ||
|
||
connection.put_data(("HTTP/1.1 %d\r\n" % 200).to_ascii_buffer()) | ||
connection.put_data(load_html("res://addons/keycloak/src/display_page.html").to_ascii_buffer()) | ||
redirect_server.stop() | ||
|
||
var parameters = _parse_url_parameters(request) | ||
var code = parameters.get("code") | ||
|
||
var data = [ | ||
"code=%s" % code, | ||
"grant_type=authorization_code", | ||
"client_id=%s" % client_id, | ||
"client_secret=%s" % client_secret, | ||
"redirect_uri=%s" % redirect_uri, | ||
] | ||
|
||
var headers: Array[String] = ["Content-Type: application/x-www-form-urlencoded"] | ||
|
||
request_token(headers, "&".join(data)) | ||
|
||
func load_html(path): | ||
if FileAccess.file_exists(path): | ||
var file = FileAccess.open(path, FileAccess.READ) | ||
var html = file.get_as_text().replace(" ", "\t").insert(0, "\n") | ||
file.close() | ||
return html | ||
|
||
func _parse_url_parameters(request: String) -> Dictionary: | ||
var parameters = {} | ||
var query = request.split("\n")[0] | ||
query = query.substr(request.find("?") + 1).split(" ")[0] | ||
var query_params = query.split("&") | ||
|
||
for param in query_params: | ||
var parts = param.split("=") | ||
if parts.size() == 2: | ||
parameters[parts[0]] = parts[1] | ||
|
||
return parameters | ||
|
||
func random_string(length): | ||
var chars = 'abcdefghijklmnopqrstuvwxyz0123456789' | ||
var word = "" | ||
var n_char = len(chars) | ||
for i in range(length): | ||
word += chars[randi() % n_char] | ||
return word | ||
|
||
func get_pub_key(): | ||
var url = "http://%s:%d/realms/%s" % [ | ||
server_addr, | ||
server_port, | ||
realm_id, | ||
] | ||
var http_request = HTTPRequest.new() | ||
http_request.name = "token_request" | ||
add_child(http_request) | ||
var error = http_request.request(url, [], HTTPClient.METHOD_GET) | ||
if error != OK: | ||
push_error("An error occurred while getting token: %s" % error) | ||
return "" | ||
var response = await http_request.request_completed | ||
http_request.queue_free() | ||
var response_body = JSON.parse_string(response[3].get_string_from_utf8()) | ||
if response_body.get("public_key"): | ||
public_key = CryptoKey.new() | ||
public_key.load_from_string( | ||
"%s\n%s\n%s" % [ | ||
"-----BEGIN PUBLIC KEY-----", | ||
response_body.get("public_key"), | ||
"-----END PUBLIC KEY-----"], | ||
true | ||
) | ||
else: | ||
push_error("Unable to get auth server public key.") | ||
|
||
func refresh_token() -> String: | ||
var url = "http://%s:%d%s" % [ | ||
server_addr, | ||
server_port, | ||
token_url, | ||
] | ||
var data = [ | ||
"grant_type=refresh_token", | ||
"refresh_token=%s" % _refresh_token, | ||
"client_id=%s" % client_id, | ||
"client_secret=%s" % client_secret, | ||
] | ||
|
||
var headers: Array[String] = ["Content-Type: application/x-www-form-urlencoded"] | ||
|
||
var http_request = HTTPRequest.new() | ||
http_request.name = "token_request" | ||
add_child(http_request) | ||
var error = http_request.request(url, headers, HTTPClient.METHOD_POST, "&".join(data)) | ||
if error != OK: | ||
push_error("An error occurred while getting token: %s" % error) | ||
return "" | ||
var response = await http_request.request_completed | ||
http_request.queue_free() | ||
var response_body = JSON.parse_string(response[3].get_string_from_utf8()) | ||
|
||
_access_token = response_body.get("access_token") | ||
_refresh_token = response_body.get("refresh_token") | ||
|
||
if not _access_token: | ||
authentication_failed.emit("Session expired.") | ||
return "" | ||
|
||
token_received.emit() | ||
return _access_token | ||
|
||
func request_token(headers: Array[String], data: String) -> void: | ||
var http_request = HTTPRequest.new() | ||
http_request.name = "token_request" | ||
add_child(http_request) | ||
http_request.request_completed.connect(_on_token_received) | ||
http_request.request_completed.connect( | ||
func(_result, _response_code, _headers, _body) -> void: | ||
http_request.queue_free() | ||
) | ||
var url = "http://%s:%d%s" % [ | ||
server_addr, | ||
server_port, | ||
token_url, | ||
] | ||
var error = http_request.request(url, headers, HTTPClient.METHOD_POST, data) | ||
if error != OK: | ||
push_error("An error occurred while getting token: %s" % error) | ||
|
||
func _on_token_received(_result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void: | ||
var response_body = JSON.parse_string(body.get_string_from_utf8()) | ||
if response_code == 200: | ||
_access_token = response_body.get("access_token") | ||
_refresh_token = response_body.get("refresh_token") | ||
|
||
token_received.emit() | ||
else: | ||
authentication_failed.emit(response_body.get("error_description")) |
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,50 @@ | ||
extends RefCounted | ||
|
||
static func auth(username: String, password: String): | ||
var headers: Array[String] = ["Content-Type: application/x-www-form-urlencoded"] | ||
var data = [ | ||
"username=%s" % username, | ||
"password=%s" % password, | ||
"client_id=%s" % Keycloak.client_id, | ||
"client_secret=%s" % Keycloak.client_secret, | ||
"grant_type=password", | ||
] | ||
Keycloak.request_token(headers, "&".join(data)) | ||
|
||
static func generate_basic_form() -> VBoxContainer: | ||
var vbox = VBoxContainer.new() | ||
var grid = GridContainer.new() | ||
grid.columns = 2 | ||
|
||
var username_label = Label.new() | ||
username_label.text = "Username" | ||
username_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT | ||
grid.add_child(username_label) | ||
|
||
var username_input = LineEdit.new() | ||
username_input.alignment = HORIZONTAL_ALIGNMENT_CENTER | ||
username_input.custom_minimum_size = Vector2(150, 0) | ||
grid.add_child(username_input) | ||
|
||
var password_label = Label.new() | ||
password_label.text = "Password" | ||
password_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT | ||
grid.add_child(password_label) | ||
|
||
var password_input = LineEdit.new() | ||
password_input.alignment = HORIZONTAL_ALIGNMENT_CENTER | ||
password_input.secret = true | ||
password_input.custom_minimum_size = Vector2(150, 0) | ||
grid.add_child(password_input) | ||
var submit_btn = Button.new() | ||
submit_btn.text = "Sign In" | ||
submit_btn.pressed.connect(func(): auth(username_input.text, password_input.text)) | ||
|
||
vbox.add_child(grid) | ||
grid.add_child(username_label) | ||
grid.add_child(username_input) | ||
grid.add_child(password_label) | ||
grid.add_child(password_input) | ||
vbox.add_child(submit_btn) | ||
|
||
return vbox |
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,35 @@ | ||
extends RefCounted | ||
|
||
static func auth(): | ||
Keycloak.set_process(true) | ||
var redir_err = Keycloak.redirect_server.listen(Keycloak.LOCAL_CLIENT_PORT, Keycloak.LOCAL_CLIENT_ADDR) | ||
if redir_err: | ||
printerr(redir_err) | ||
|
||
var data = [ | ||
"response_type=code", | ||
"scope=openid", | ||
"client_id=%s" % Keycloak.client_id, | ||
"client_secret=%s" % Keycloak.client_secret, | ||
"redirect_uri=%s" % Keycloak.redirect_uri, | ||
"state=%s" % Keycloak.random_string(22), | ||
"nonce=%s" % Keycloak.random_string(22), | ||
"login_hint=google", | ||
"kc_idp_hint=google", | ||
] | ||
|
||
OS.shell_open("http://%s:%d%s?%s" % [ | ||
Keycloak.server_addr, | ||
Keycloak.server_port, | ||
Keycloak.auth_url, | ||
"&".join(data) | ||
]) | ||
|
||
static func generate_google_btn() -> Button: | ||
var google_btn = Button.new() | ||
google_btn.icon = load("res://addons/keycloak/textures/google-logo.png") | ||
google_btn.expand_icon = true | ||
google_btn.text = "Sign in with Google" | ||
google_btn.pressed.connect(func(): auth()) | ||
|
||
return google_btn |
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,23 @@ | ||
<html> | ||
<style> | ||
body { | ||
position: absolute; | ||
background-color: 1A1A1A; | ||
font-family: arial; | ||
font-weight: bold; | ||
font-size: 24px; | ||
top: 50%; | ||
left: 50%; | ||
-ms-transform: translate(-50%, -50%); | ||
transform: translate(-50%, -50%); | ||
text-align: center; | ||
vertical-align: middle; | ||
} | ||
</style> | ||
|
||
<body> | ||
<h1 style="color:e0e0e0;">Success!</h2> | ||
<h2 style="color:e0e0e0;">You can close this window.</h2> | ||
</body> | ||
|
||
</html> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
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,34 @@ | ||
[remap] | ||
|
||
importer="texture" | ||
type="CompressedTexture2D" | ||
uid="uid://bilnswoplg03f" | ||
path="res://.godot/imported/google-logo.png-c55412ecf17f85293aa015c617f58dab.ctex" | ||
metadata={ | ||
"vram_texture": false | ||
} | ||
|
||
[deps] | ||
|
||
source_file="res://addons/keycloak/textures/google-logo.png" | ||
dest_files=["res://.godot/imported/google-logo.png-c55412ecf17f85293aa015c617f58dab.ctex"] | ||
|
||
[params] | ||
|
||
compress/mode=0 | ||
compress/high_quality=false | ||
compress/lossy_quality=0.7 | ||
compress/hdr_compression=1 | ||
compress/normal_map=0 | ||
compress/channel_pack=0 | ||
mipmaps/generate=false | ||
mipmaps/limit=-1 | ||
roughness/mode=0 | ||
roughness/src_normal="" | ||
process/fix_alpha_border=true | ||
process/premult_alpha=false | ||
process/normal_map_invert_y=false | ||
process/hdr_as_srgb=false | ||
process/hdr_clamp_exposure=false | ||
process/size_limit=0 | ||
detect_3d/compress_to=1 |