nextCloud authentication via FabMan

Hello community,

I’ve just managed to set up nextCloud in a way that users can authenticate with their FabMan credentials.
If anyone is interested I can share how it is done.

1 Like

Hi Walter,

sure, I’d be curious how you did that! :blush:

You can send me more info. Thanks!

There’s an extension for nextCloud - or rather, an “App” called “External user authentication”.
Install and enable it. Out of the box it is not compatible with Fabman, but here comes the hack:

The app’s files are in a directory inside nextCloud’s data folder; I’m using a docker container, but you should be able to find this on any system. Inside the container, nextCloud files are under /var/www/html (which is the data directory that is usally bound to a volume or directory on the host system). Go to “custom_apps/user_external/lib” inside this folder.

The files inside are called “BasicAuth.php”, “WebDavAuth.php” etc.

Create a new file (make it readably for nextCloud) named “fabman.php” with this content:

<?php

namespace OCA\UserExternal;

class fabman extends Base {
	private $authUrl;
	private $fabmanAccount;
	public function __construct($fabmanAccount) {
		$this->authUrl = $authUrl = "https://fabman.io/api/v1/user/login";
		parent::__construct($this->authUrl);
		$this->fabmanAccount = $fabmanAccount;
	}
	/**
	 * Check if the password is correct
	 * @param string $email The user's email address (same as for fabman login)
	 * @param string $password The password
	 * @return true/false
	 */
	public function checkPassword($email, $password) {		
		$context = stream_context_create(array(
			'http' => array(
				'method' => "POST",
				'header'=> [ 'Content-Type: application/json' ],
				'content' => json_encode(Array(
					"emailAddress" => $email,
					"password" => $password
				)),
			))
		);		
		$headers = get_headers($this->authUrl, 1, $context);
		if (!$headers) {
			\OC::$server->getLogger()->error(
				'ERROR: Not possible to connect to FabMan: '.$this->authUrl,
				['app' => 'user_external']
			);
			return false;
		}		
		foreach ($headers as $header) {			
			if (!empty($header) && gettype($header)=="string" && substr($header, 0, 12)=="HTTP/1.1 200") {
				$response = file_get_contents($this->authUrl, 1, $context);
				$response = json_decode($response, true);
				if (empty($response)) {
					\OC::$server->getLogger()->error(
						'ERROR: Invalid response from FabMan: '.$this->authUrl,
						['app' => 'user_external']
					);
					return false;
				}
				else if ($response['state']!='active') {
					\OC::$server->getLogger()->error(
						'ERROR: FabMan user is not active: '.$email,
						['app' => 'user_external']
					);
					return false;
				}
				else if (empty($response['members'])) {
					\OC::$server->getLogger()->error(
						'ERROR: No FabMan members assigned to FabMan user: '.$email,
						['app' => 'user_external']
					);
					return false;
				}
				foreach ($response['members'] as $data) {
					if (empty($data) ) {
						\OC::$server->getLogger()->error(
							'ERROR: Invalid member data from FabMan: '.$this->authUrl,
							['app' => 'user_external']
						);
						return false;
					}
					else if ($data['account'] != $this->fabmanAccount) {
						// not my maker space!
						continue;
					}
					else if ($data['state'] != 'active') {
						\OC::$server->getLogger()->error(
							'ERROR: FabMan Member is inactive: '.$data['firstName'].' '.$data['lastName'],
							['app' => 'user_external']
						);					
						return false;
					}
					else if (empty($data['metadata']) ) {
						\OC::$server->getLogger()->error(
							'ERROR: No metadata field (invalid data) for FabMan member : '.$data['firstName'].' '.$data['lastName'],
							['app' => 'user_external']
						);
						return false;
					}
					else if (empty($data['metadata']['nextCloud'])) {
						\OC::$server->getLogger()->error(
							'ERROR: nextCloud not enabled for Fabman Member: '.$data['firstName'].' '.$data['lastName'],
							['app' => 'user_external']
						);
						return false;					
					}					
					$uid = $data['metadata']['nextCloud'];
					\OC::$server->getLogger()->error(
						'OK: FabMan login succeeded: '.$data['firstName'].' '.$data['lastName'].' as '.$uid,
						['app' => 'user_external']
					);
					$this->storeUser($uid);
					return $uid;	
				}				
				\OC::$server->getLogger()->error(
					'ERROR: No valid FabMan member found: '.$email.' @ '.$this->authUrl,
					['app' => 'user_external']
				);
				return false;

			}
		}
		\OC::$server->getLogger()->error(
			'ERROR: FabMan login failed: '.$email.' @ '.$this->authUrl,
			['app' => 'user_external']
		);
		return false;		
	}
}

Finally, locate nextCloud’s config file (config/config.php in the nextCloud data folder) and add this just before the line that contains only “);”:

  'user_backends' => 
	  array (
		0 => 
		array (
		  'class' => '\\OCA\\UserExternal\\fabman',
		  'arguments' => array ( 'fabmanAccount'=><fabman account id>),
		),
	  ),

The “fabman account id” is the ID of your space’s account. You see it in the address bar when you’re in your space’s Fabman admin area.

Now you’re almost there.
My script does several checks in order to validate the user (is fabmans response valid, is username&password correct, is the user active, is it a member of my space, is it an active member of my space, … a few of these are probably not needed). The final check is to see if the member has the metadata field “nextCloud” set in Fabman - if yes, the user is allowed to log in WITH username = value of metadata.nextCloud.

(Side note: you don’t need to create a new account for every member within nextCloud. Just set the metadata field “nextCloud” to the username you want for this member, and nextCloud creates it’s user account when the login is successful.)

This behaviour can obviously be changed. You could remove the two else if blocks that check the metadata content, thus allowing any valid fabman member using the cloud. Just change the $uid to your desired user name:
$uid = $data[‘metadata’][‘nextCloud’];
could be
$uid = $data[‘firstName’].$data[‘lastName’].$data[‘id’];
or any other unique (!!) name.

Good luck! (And just ask if anything is not clear.)

1 Like