diff options
author | Freya Murphy <freya@freyacat.org> | 2024-05-30 13:05:46 -0400 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2024-05-30 13:05:46 -0400 |
commit | 39bcb09a367251bed7cfb445f546252547058e66 (patch) | |
tree | a1bb8e2c137e16202836ea6df8d7004b5e48e8a6 /src/web/helpers | |
parent | am dumb (diff) | |
download | ldap_forwardauth-39bcb09a367251bed7cfb445f546252547058e66.tar.gz ldap_forwardauth-39bcb09a367251bed7cfb445f546252547058e66.tar.bz2 ldap_forwardauth-39bcb09a367251bed7cfb445f546252547058e66.zip |
many changes
Diffstat (limited to 'src/web/helpers')
-rw-r--r-- | src/web/helpers/auth.php | 140 | ||||
-rw-r--r-- | src/web/helpers/ldap.php | 133 | ||||
-rw-r--r-- | src/web/helpers/schema.php | 236 |
3 files changed, 411 insertions, 98 deletions
diff --git a/src/web/helpers/auth.php b/src/web/helpers/auth.php index 9228706..187f556 100644 --- a/src/web/helpers/auth.php +++ b/src/web/helpers/auth.php @@ -1,85 +1,83 @@ <?php /* Copyright (c) 2024 Freya Murphy */ -$keys = array(); +class AuthHelper { -function get_cookie() { - $cookie_name = 'X-LDAP-Auth-Key'; - if(isset($_COOKIE[$cookie_name])) { - return $_COOKIE[$cookie_name]; - } else { - return FALSE; - } -} - -function store_cookie($key) { - $cookie_name = 'X-LDAP-Auth-Key'; - $cookie_options = array ( - 'expires' => time() + 60*60*24*30, - 'path' => '/', - 'domain' => getenv("COOKIE_DOMAIN"), - 'secure' => true, - 'httponly' => true, - 'samesite' => 'None' - ); - setcookie( - $cookie_name, - $key, - $cookie_options - ); -} + private $session_lifetime_seconds; -function load_key($key) { - $file = "/tmp/$key"; - if (!file_exists($file)) - return FALSE; - $content = explode("\n", file_get_contents($file)); - return array( - 'user' => $content[0], - 'time' => $content[1] - ); -} + function __construct() { + $this->session_lifetime_seconds = 60 * 60 * 24 * 3; + } -function store_key($key, $user) { - $file = "/tmp/$key"; - $now = (string)time(); - $content = "$user\n{$now}"; - file_put_contents($file, $content, LOCK_EX); -} + /** + * Generate a random token + * @param int $length + */ + private function gen_token(int $length): string { + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $random = ''; -function get_random($n) -{ - $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $randomString = ''; + for ($i = 0; $i < $length; $i++) { + $index = rand(0, strlen($characters) - 1); + $random .= $characters[$index]; + } - for ($i = 0; $i < $n; $i++) { - $index = rand(0, strlen($characters) - 1); - $randomString .= $characters[$index]; - } + return $random; + } - return $randomString; -} + /** + * Saves a user into the session specified by their auth key + * @param Session $session - the session user data + */ + public function save_session(Session $session): void { + $path = "/tmp/{$session->token}"; + $data = json_encode($session->to_array()); + file_put_contents($path, $data, LOCK_EX); + } -function key_auth() { - $key = get_cookie(); - if ($key === FALSE) { - return FALSE; + /** + * Loads the auth session associated with a specific key + * @param string $token - the session $key + */ + private function load_session(string $token): ?Session { + try { + $path = "/tmp/$token"; + if (!file_exists($path)) { + return NULL; + } + $content = file_get_contents($path); + $json = json_decode($content, TRUE); + $session = new Session(); + if ($session->from_array($json)) + return NULL; + return $session; + } catch (Exception $e) { + return NULL; + } } - $data = load_key($key); - if ($data === FALSE) { - return FALSE; + + /** + * Creates a new session for a user + */ + public function create_session(User $user): Session { + $session = new Session(); + $session->token = $this->gen_token(128); + $session->created = time(); + $session->user = $user; + $session->reset_expiry(); + $this->save_session($session); + return $session; } - $user = $data['user']; - $time = $data['time']; - $now = time(); - if ($time > $now || $now - $time > 60 * 60 * 24) { - return FALSE; + + /** + * Gets the current authed session + */ + public function get_session(): ?Session { + $cookie_name = getenv("COOKIE_NAME"); + if(!isset($_COOKIE[$cookie_name])) { + return NULL; + } + $token = $_COOKIE[$cookie_name]; + return $this->load_session($token); } - store_key($key, $user); - return $user; -} -function key_new($user) { - $key = get_random(128); - store_key($key, $user); - store_cookie($key); } diff --git a/src/web/helpers/ldap.php b/src/web/helpers/ldap.php index f3697cc..46bbe69 100644 --- a/src/web/helpers/ldap.php +++ b/src/web/helpers/ldap.php @@ -1,41 +1,120 @@ <?php /* Copyright (c) 2024 Freya Murphy */ -function ldap_auth($auth_username, $auth_password) { - $url = getenv("LDAP_URL"); - $bind = getenv("LDAP_BIND_DN"); - $password = getenv("LDAP_BIND_PASSWORD"); - $bound = getenv("LDAP_BASE_DN"); - $filter = getenv("LDAP_FILTER"); - $uid = getenv("LDAP_UID"); +class LDAPHelper { - $conn = @ldap_connect($url); - if (!$conn) { - return NULL; + private ?\LDAP\Connection $conn; + private array $env; + private array $matchers; + + private ?string $bound; + + function __construct() { + $this->env = array( + # ldap host + 'url' => getenv("LDAP_URL"), + # ldap credentials + 'bind' => getenv("LDAP_BIND_DN"), + 'password' => getenv("LDAP_BIND_PASSWORD"), + # ldap search + 'base' => getenv("LDAP_BASE_DN"), + 'filter' => getenv("LDAP_FILTER"), + 'uid' => getenv("LDAP_UID"), + ); + + $this->matchers = array( + 'username' => getenv("LDAP_USERNAME_MATCHER"), + 'email' => getenv("LDAP_EMAIL_MATCHER"), + 'first_name' => getenv("LDAP_FIRST_NAME_MATCHER"), + 'last_name' => getenv("LDAP_LAST_NAME_MATCHER"), + ); + + $this->bound = NULL; + $this->conn = NULL; + } + + private function connect(): int { + if (($this->conn = @ldap_connect($this->env['url'])) == FALSE) { + $this->conn = NULL; + return 1; + } + @ldap_set_option($this->conn, LDAP_OPT_PROTOCOL_VERSION, 3); + return 0; } - ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3); - $bind_conn = @ldap_bind($conn, $bind, $password); - if (!$bind_conn) { - return NULL; + private function rebind(): int { + if ($this->bound != $this->env['bind']) { + return $this->bind( + $this->env['bind'], + $this->env['password']); + } + return 0; } - $search = @ldap_search($conn, $bound, $filter); + public function bind( + string $dn, + #[\SensitiveParameter] string $password + ): int { + if ($this->conn == NULL && $this->connect()) { + return 1; + } + if (@ldap_bind($this->conn, $dn, $password) == FALSE) { + return 1; + } + $this->bound = $dn; + return 0; + } - $info = @ldap_get_entries($conn, $search); - $user = NULL; - for ($i=0; $i<$info['count']; $i++) { - $user = $info[$i]; - if (!array_key_exists($uid, $user)) - continue; - if ($user[$uid][0] == $auth_username) - break; + /** + * @param array<int,mixed> $user + */ + private function find_entry(array $user, string $field): mixed { + if (!isset($user[$field])) + return NULL; + $data = $user[$field]; + if (is_array($data)) + $data = $data[0]; + return $data; } - if ($user == NULL) { - return FALSE; + public function search( + string ...$usernames + ): ?array { + if ($this->rebind()) + return NULL; + + $search = @ldap_search( + $this->conn, + $this->env['base'], + $this->env['filter'] + ); + if ($search == FALSE) + return NULL; + + $info = @ldap_get_entries($this->conn, $search); + + $users = array(); + + for ($i=0; $i<$info['count']; $i++) { + $user_arr = $info[$i]; + $user_data = array ( + 'dn' => $user_arr['dn'], + 'username' => $this->find_entry($user_arr, $this->matchers['username']), + 'email' => $this->find_entry($user_arr, $this->matchers['email']), + 'first_name' => $this->find_entry($user_arr, $this->matchers['first_name']), + 'last_name' => $this->find_entry($user_arr, $this->matchers['last_name']) + ); + $user = new User(); + if ($user->from_array($user_data)) { + continue; + } + if (count($usernames) && !in_array($user->username, $usernames)) { + continue; + } + $users[] = $user; + } + + return $users; } - $succ = @ldap_bind($conn, $user['dn'], $auth_password); - return !!$succ; } diff --git a/src/web/helpers/schema.php b/src/web/helpers/schema.php new file mode 100644 index 0000000..6afa43f --- /dev/null +++ b/src/web/helpers/schema.php @@ -0,0 +1,236 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ + +class User { + + public ?string $dn; + public ?string $username; + public ?string $email; + public ?string $first_name; + public ?string $last_name; + + function __construct() {} + + /** + * Validates all required fields are set + */ + private function validate(): int { + return ( + $this->dn == NULL || + $this->username == NULL || + $this->email == NULL + ) ? 1 : 0; + } + + /** + * Loads Data from the array to self + * @param array $data - the data to load + * @return int 0 on success, 1 on error + */ + public function from_array(array $data): int { + $this->dn = NULL; + $this->username = NULL; + $this->email = NULL; + $this->first_name = NULL; + $this->last_name = NULL; + + foreach ($data as $key => $value) { + if ($value == NULL) + continue; + $type = gettype($value); + switch ($key) { + case 'dn': { + if ($type != 'string') + return 1; + $this->dn = $value; + } break; + case 'username': { + if ($type != 'string') + return 1; + $this->username = $value; + } break; + case 'email': { + if ($type != 'string') + return 1; + $this->email = $value; + } break; + case 'first_name': { + if ($type != 'string') + return 1; + $this->first_name = $value; + } break; + case 'last_name': { + if ($type != 'string') + return 1; + $this->last_name = $value; + } break; + } + } + + return $this->validate(); + } + + /** + * Converts the user into an array + * @return ?array<string,string> + */ + public function to_array(): ?array { + if ($this->validate()) + return NULL; + $data = array( + 'dn' => $this->dn, + 'username' => $this->username, + 'email' => $this->email + ); + if ($this->first_name) + $data['first_name'] = $this->first_name; + if ($this->last_name) + $data['last_name'] = $this->last_name; + return $data; + } + + /** + * Writes the HTTP headers + */ + public function write_headers(): int { + if ($this->validate()) + return 1; + + $header_username = getenv("HTTP_USERNAME_HEADER"); + $header_email = getenv("HTTP_EMAIL_HEADER"); + $header_first = getenv("HTTP_FIRST_NAME_HEADER"); + $header_last = getenv("HTTP_LAST_NAME_HEADER"); + + header("{$header_username}: {$this->username}"); + header("{$header_email}: {$this->email}"); + if ($this->first_name) + header("{$header_first}: {$this->first_name}"); + if ($this->last_name) + header("{$header_last}: {$this->last_name}"); + + return 0; + } + +} + +class Session { + + public ?User $user; + public ?int $created; + public ?int $expires; + public ?string $token; + + private int $session_lifetime_seconds; + + function __construct() { + $this->session_lifetime_seconds = 60 * 60 * 24 * 3; + } + + /** + * Validates all required fields are set + */ + private function validate(): int { + if ( + $this->user == NULL || + $this->created == NULL || + $this->expires == NULL || + $this->token == NULL + ) { + return 1; + } + if ($this->expires < time()) + return 1; + return 0; + } + + /** + * Loads Data from the array to self + * @param array $data - the data to load + * @return int 0 on success, 1 on error + */ + public function from_array(array $data): int { + $this->user = NULL; + $this->created = NULL; + $this->expires = NULL; + $this->token = NULL; + + foreach ($data as $key => $value) { + if ($value == NULL) + continue; + $type = gettype($value); + switch ($key) { + case 'user': { + $this->user = new User(); + if ($this->user->from_array($value)) + return 1; + } break; + case 'created': { + if ($type != 'integer') + return 1; + $this->created = $value; + } break; + case 'expires': { + if ($type != 'integer') + return 1; + $this->expires = $value; + } break; + case 'token': { + if ($type != 'string') + return 1; + $this->token = $value; + } break; + } + } + + return $this->validate(); + } + + /** + * Renew the expiry clock + */ + public function reset_expiry(): void { + $this->expires = time() + $this->session_lifetime_seconds; + } + + /** + * Converts the session into an array + * @return ?array<string,mixed> + */ + public function to_array(): ?array { + if ($this->validate()) + return NULL; + return array( + 'user' => $this->user->to_array(), + 'created' => $this->created, + 'expires' => $this->expires, + 'token' => $this->token + ); + } + + /** + * Writes the HTTP headers + */ + public function write_headers(): int { + if ($this->validate()) + return 1; + if ($this->user->write_headers()) + return 1; + + $cookie_name = getenv("COOKIE_NAME"); + $cookie_options = array ( + 'expires' => $this->expires, + 'path' => '/', + 'domain' => getenv("COOKIE_DOMAIN"), + 'secure' => true, + 'httponly' => true, + 'samesite' => 'Lax' + ); + setcookie( + $cookie_name, + $this->token, + $cookie_options + ); + + return 0; + } + +} |