summaryrefslogblamecommitdiffstats
path: root/inc/user.inc.php
blob: 84e517dd1f8f3e271ede2c8adae4b0048534951f (plain) (tree)
1
2
3
4
5
6
7
8
9
10




          




                                                   
 
                                                 
         
                                            

         
                                                       



                                     
                                                   



                                     
                                                  
         
                                                                       

         
                                                  



                                          
                                                



                                   
                                               

                                                  
                                    
                                             

         
                                                 

                                                 
                                    


                                            
                                                 

                                        
                                    


                                                                                
                                                      

                                        
                                    


                                                
                                                     

                                        
                                    


                                               
                                                  
         
                                                                                                                   

         
                                              
         
                                                                                     
         
        
                                              
         

                                                                                     
         
 




                                                 
                                                           


                                                   
                                    


                                              
                                                             


                                               
                                    


                                    




                                                    
                                                                 

                                                       
                                    


                                                   



                                                                      
                                                        

                                        
                                    
                                                   
                                                                                                           
                                                                                            
                                                                                                                       
                                                                           


                                           






                                                 
 
                                           
         
                                                                                    

                                       
                                              
                                                       

                                                                          
                                                                                                                     
                                                                                                                           
                                                                                                                                                       
                                 
                                                  
                                                                                                                                                
                                             
                         
                                                 
                                                                                                                                      
                                                                                                              

                                                                   


                                                  
                                             

                                 
                                   





                                                                                            


                                                                                                                                                  

                                                                                 
                 
                                     

                                                      



                                                   






                                                                                 
                                    
                                         
                                               
                                                             
                                                               


                                                                           
                                                      
                                                 
                                                                                                                        
                                                              
                 
                                                                                                                                                                                               

                                                              
                                               

                                                                                                            



                                                                      
                                                        



                                                         
                                                                                
                                     
                                                                                             






                                                                           
                                                                                           


                                                      
 




                                                                                                                                          



                                                                                                                                                   
                                                                                   
                                                                                  
                                                                          


                                                                                                                                                                      
                                                                                   
                                                                                  
                                                                           








                                                                                        
                                 
                                                                                                                       
                                                                                 
                                                                                                                             

                                                                  

                           
                                                                                                                       
                                                                                                    
                                                                                                                                                                     




                                                                        

                           
                            

         
                                                                 




                                                                                                             

                                                       



                                 
                                                             



                                                                                                          

                                                       
                   
                                                                   
         
 
                                                                      
         
                                                                                                                                       

                                     
                                                             







                                                                                                                                                                                              
                                              
         


                                                            
                                                                                                                                                          
                 
                                  
                                    
                                                                   


                                                     


                        
                                             





                                                                                                                                     
 
<?php

class User
{

	private static ?array $user = null;
	private static ?array $organization = NULL;
	private static bool $isShib = false;
	private static bool $isInDb = false;
	private static bool $isAnonymous = false;

	public static function isLoggedIn(): bool
	{
		return self::$user !== null;
	}

	public static function isShibbolethAuth(): bool
	{
		return self::$isShib;
	}

	public static function isInDatabase(): bool
	{
		return self::$isInDb;
	}

	public static function isLocalOnly(): bool
	{
		return self::$user !== null && self::$isShib === false;
	}

	public static function isAnonymous(): bool
	{
		return self::$isAnonymous;
	}

	public static function getData(): ?array
	{
		return self::$user;
	}

	public static function getId(): ?string
	{
		if (!isset(self::$user['userid']))
			return null;
		return self::$user['userid'];
	}

	public static function getMail(): ?string
	{
		if (!isset(self::$user['email']))
			return null;
		return self::$user['email'];
	}

	public static function getName(): ?string
	{
		if (!self::isLoggedIn())
			return null;
		return self::$user['firstname'] . ' ' . self::$user['lastname'];
	}

	public static function getFirstName(): ?string
	{
		if (!self::isLoggedIn())
			return null;
		return self::$user['firstname'];
	}

	public static function getLastName(): ?string
	{
		if (!self::isLoggedIn())
			return null;
		return self::$user['lastname'];
	}

	public static function hasFullName(): bool
	{
		return self::$user !== null && !empty(self::$user['firstname']) && !empty(self::$user['lastname']);
	}

	public static function isTutor(): bool
	{
		return isset(self::$user['role']) && self::$user['role'] === 'TUTOR';
	}
	
	public static function isAdmin(): bool
	{
		// TODO: per Institution...
		return in_array(self::getShibId(), unserialize(CONFIG_ADMINS), true);
	}

	/**
	 * Organization ID used locally in our DB
	 *
	 * @return string
	 */
	public static function getOrganizationId(): ?string
	{
		$org = self::getOrganization();
		if (!isset($org['organizationid']))
			return null;
		return $org['organizationid'];
	}

	public static function getOrganizationName(): ?string
	{
		$org = self::getOrganization();
		if (!isset($org['name']))
			return null;
		return $org['name'];
	}

	/**
	 * Organization ID as supplied by shibboleth
	 *
	 * @return string
	 */
	public static function getRemoteOrganizationId(): ?string
	{
		if (empty(self::$user['organization']))
			return null;
		return self::$user['organization'];
	}

	/**
	 * Return user's organization, or null if not known in our DB.
	 * @return ?array{organizationid: string, name: string}
	 */
	public static function getOrganization(): ?array
	{
		if (!self::isLoggedIn())
			return null;
		if (is_null(self::$organization)) {
			$org = Database::queryFirst('SELECT organizationid, name FROM organization_suffix '
					. ' INNER JOIN organization USING (organizationid) '
					. ' WHERE suffix = :org LIMIT 1', array('org' => self::$user['organization']));
			self::$organization = $org !== false ? $org : null;
		}
		return self::$organization;
	}
	
	public static function getShibId()
	{
		if (empty(self::$user['shibid']))
			return false;
		return self::$user['shibid'];
	}

	public static function load(): bool
	{
		//file_put_contents('/tmp/test-' . time(), print_r($_SERVER, true));
		if (self::isLoggedIn())
			return true;
		$hasSession = Session::load();
		if (empty($_SERVER['persistent-id'])) {
			if (Session::getUid() === false) {
				if (!empty($_SERVER['Shib-Session-ID'])) {
					Message::addError('Sie haben sich erfolgreich mittels {{0}} authentifiziert,'
						. ' aber der IdP Ihrer Einrichtung scheint die benötigten Metadaten nicht'
						. ' an den {{1}}-SP zu übermitteln. Bitte wenden Sie sich an den Support.', CONFIG_IDM, CONFIG_SUITE);
				}
				Session::delete();
				file_put_contents('/tmp/shib-load-' . time() . '-' . $_SERVER['REMOTE_ADDR'] . '.txt', print_r($_SERVER, true));
				return false;
			}
			// Try user from local DB
			$usr = Database::queryFirst('SELECT userid, shibid, organizationid AS organization, firstname, lastname, email
					FROM user WHERE userid = :uid LIMIT 1', ['uid' => Session::getUid()]);
			self::$user = $usr !== false ? $usr : null;
			self::$isInDb = self::$user !== null;
			if (!self::$isInDb) {
				Session::delete();
			}
			return self::$isInDb;
		}
		// Try bwIDM etc.
		if (!$hasSession) {
			// Make sure cookies are enabled
			if (!empty($_SERVER['Shib-Session-ID'])) {
				if (isset($_GET['force-cookie']))
					die('Bitte aktivieren Sie Cookies und Javascript!');

			}
			Session::create();
			Session::set('token', md5(mt_rand() . $_SERVER['REMOTE_ADDR'] . microtime(true) . $_SERVER['persistent-id'] . mt_rand()));
			Session::save();
			if (!empty($_SERVER['Shib-Session-ID']))
				Util::redirect('?do=Main&force-cookie=true.dat');
		}
		self::$isShib = true;
		if (!isset($_SERVER[CONFIG_SURNAME]))
			$_SERVER[CONFIG_SURNAME] = '';
		if (!isset($_SERVER['givenName']))
			$_SERVER['givenName'] = '';
		if (!isset($_SERVER['mail']))
			$_SERVER['mail'] = '';
		$shibId = [];
		if (strpos($_SERVER['persistent-id'], ';') !== false) {
			foreach (explode(';', $_SERVER['persistent-id']) as $s) {
				$shibId[] = md5($s);
			}
		}
		$shibId[] = md5($_SERVER['persistent-id']);
		self::$user = array(
			'userid' => NULL,
			'shibid' => $shibId[0],
			'firstname' => $_SERVER['givenName'],
			'lastname' => $_SERVER[CONFIG_SURNAME],
			'email' => $_SERVER['mail'],
		);
		// Figure out whether the user should be considered a tutor
		self::$user['role'] = Util::getRole();
		// Try to figure out organization
		if (isset($_SERVER[CONFIG_EPPN]) && preg_match('/@([0-9a-zA-Z\-._]+)$/', $_SERVER[CONFIG_EPPN], $out)) {
			self::$user['organization'] = $out[1];
		}
		if (!isset(self::$user['organization']) && isset($_SERVER[CONFIG_SCOPED_AFFILIATION]) && preg_match('/@([0-9a-zA-Z\-._]+)(;|$)/', $_SERVER[CONFIG_SCOPED_AFFILIATION], $out)) {
			self::$user['organization'] = $out[1];
		}
		// Get matching db entry if any
		$user = Database::queryFirst('SELECT userid, firstname, lastname, email, fixedname FROM user
				WHERE shibid IN (:shibid) LIMIT 1', ['shibid' => $shibId]);
		if ($user === false) {
			// No match in database, user is not signed up
			return true;
		}
		self::$user['userid'] = $user['userid'];
		if (Session::getUid() === false) {
			Session::setUid($user['userid']);
			Session::save();
		}
		// Already signed up, see if we can fetch missing fields from DB
		self::$isInDb = true;
		self::$isAnonymous = (empty($user['firstname']) && empty($user['lastname']));
		foreach (array('firstname', 'lastname', 'email') as $key) {
			if (empty(self::$user[$key]))
				self::$user[$key] = $user[$key];
		}
		return true;
	}

	public static function deploy(bool $anonymous, ?string $existingLogin = null): bool
	{
		if (empty(self::$user['shibid']))
			Util::traceError('NO SHIBID');

		if (self::getOrganizationId() === null) {
			Message::addError('Your home organization ID {{0}} is not known to this server', self::getRemoteOrganizationId());
			Util::redirect('?do=Main');
		}

		// Merging with test-account:
		if (!empty($existingLogin)) {
			if ($anonymous) {
				$ret = Database::exec("UPDATE user SET shibid = :shibid, firstname = '', lastname = '', email = '', password = '' "
					. " WHERE userid = :userid LIMIT 1", array(
						'shibid' => self::$user['shibid'],
						'userid' => $existingLogin
					));
			} else {
				$ret = Database::exec("UPDATE user SET shibid = :shibid, password = '', firstname = :firstname, lastname = :lastname, email = :email "
					. " WHERE userid = :userid LIMIT 1", array(
						'shibid' => self::$user['shibid'],
						'userid' => $existingLogin,
						'firstname' => self::$user['firstname'],
						'lastname' => self::$user['lastname'],
						'email' => self::$user['email']
					));
			}
			return $ret > 0;
		}
		
		// New account
		if ($anonymous) {
			Database::exec("INSERT INTO user (shibid, userid, organizationid, firstname, lastname, email) "
				. " VALUES (:shibid, :shibid, :org, '', '', '') "
				. " ON DUPLICATE KEY UPDATE firstname = '', lastname = '', email = '', password = ''", array(
				'shibid' => self::$user['shibid'],
				'org' => self::getOrganizationId()
			));
		} else {
			Database::exec("INSERT INTO user (shibid, userid, organizationid, firstname, lastname, email) "
				. " VALUES (:shibid, :shibid, :org, :firstname, :lastname, :email) "
				. " ON DUPLICATE KEY UPDATE firstname = VALUES(firstname), lastname = VALUES(lastname), email = VALUES(email), password = ''", array(
				'shibid' => self::$user['shibid'],
				'firstname' => self::$user['firstname'],
				'lastname' => self::$user['lastname'],
				'email' => self::$user['email'],
				'org' => self::getOrganizationId()
			));
		}
		return true;
	}

	public static function updatePassword(string $pass): bool
	{
		if (!self::isLoggedIn() || self::$isShib || !self::$isInDb)
			return false;
		$pw = Crypto::hash6($pass);
		$ret = Database::exec('UPDATE user SET password = :pass WHERE userid = :user LIMIT 1', array(
				'pass' => $pw,
				'user' => self::getId()
		));
		return $ret == 1;
	}

	public static function updateMail(string $mail): bool
	{
		if (!self::isLoggedIn() || self::$isShib || !self::$isInDb)
			return false;
		$ret = Database::exec('UPDATE user SET email = :mail WHERE userid = :user LIMIT 1', array(
				'mail' => $mail,
				'user' => self::getId()
		));
		return $ret == 1 || $mail === self::$user['email'];
	}

	public static function login(string $user, string $pass): bool
	{
		$ret = Database::queryFirst('SELECT userid, password FROM user WHERE userid = :user LIMIT 1', array(':user' => $user));
		if ($ret === false)
			return false;
		if (!Crypto::verify($pass, $ret['password']))
			return false;
		Session::create();
		Session::setUid($ret['userid']);
		Session::set('token', md5(rand() . time() . mt_rand() . $_SERVER['REMOTE_ADDR'] . rand() . $_SERVER['REMOTE_PORT'] . rand() . $_SERVER['HTTP_USER_AGENT'] . microtime(true)));
		Session::save();
		return true;
	}

	public static function logout(): never
	{
		foreach ($_COOKIE as $name => $value) {
			if (substr($name, 0, 5) !== '_shib')
				continue;
			@setcookie($name, '', time() - CONFIG_SESSION_TIMEOUT, null, null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true);
		}
		Session::delete();
		if (self::$isShib) {
			Header('Location: /Shibboleth.sso/Logout');
		} else {
			Header('Location: ?do=Main');
		}
		exit(0);
	}

	public static function delete(): bool
	{
		if (!User::isLoggedIn() || !User::isInDatabase())
			return true;
		return Database::exec("DELETE FROM user WHERE userid = :userid LIMIT 1", array('userid' => User::getId()), true) > 0;
	}

}