summaryrefslogtreecommitdiff
path: root/src/web/helpers
diff options
context:
space:
mode:
authorFreya Murphy <freya@freyacat.org>2024-05-30 13:05:46 -0400
committerFreya Murphy <freya@freyacat.org>2024-05-30 13:05:46 -0400
commit39bcb09a367251bed7cfb445f546252547058e66 (patch)
treea1bb8e2c137e16202836ea6df8d7004b5e48e8a6 /src/web/helpers
parentam dumb (diff)
downloadldap_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.php140
-rw-r--r--src/web/helpers/ldap.php133
-rw-r--r--src/web/helpers/schema.php236
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;
+ }
+
+}