diff --git a/airtime_mvc/application/auth/adapters/FreeIpa.php b/airtime_mvc/application/auth/adapters/FreeIpa.php
new file mode 100644
index 000000000..17217e275
--- /dev/null
+++ b/airtime_mvc/application/auth/adapters/FreeIpa.php
@@ -0,0 +1,117 @@
+username = $username;
+ return $this;
+ }
+
+ /**
+ * password from form
+ *
+ * This is ignored by FreeIPA but needs to get passed for completeness
+ *
+ * @return self
+ */
+ function setCredential($password) {
+ $this->password = $password;
+ return $this;
+ }
+
+ /**
+ * Check if apache logged the user and get data from ldap
+ *
+ * @return Zend_Auth_Result
+ */
+ function authenticate()
+ {
+ if (array_key_exists('EXTERNAL_AUTH_ERROR', $_SERVER)) {
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null, array($_SERVER['EXTERNAL_AUTH_ERROR']));
+ }
+ if (!array_key_exists('REMOTE_USER', $_SERVER)) {
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null);
+ }
+ // success, the user is good since the service populated the REMOTE_USER
+ $remoteUser = $_SERVER['REMOTE_USER'];
+
+ $subj = CcSubjsQuery::create()->findOneByDbLogin($remoteUser);
+ $subjId =null;
+ if ($subj) {
+ $subjId = $subj->getDBId();
+ }
+
+ if ($subjId) {
+ $user = new Application_Model_User($subjId);
+ } else {
+ // upsert the user on login for first time users
+ $user = new Application_Model_User('');
+ }
+
+ // Always zap any local info with new info from ipa
+ $user->setLogin($remoteUser);
+
+ // Use a random password for IPA users, reset on each login... I may change this to get set to the IPA pass but hate that it is being stored as md5 behind the scenes
+ // gets rescrambled on each succeful login for security purposes
+ $ipaDummyPass = bin2hex(openssl_random_pseudo_bytes(10));
+ $user->setPassword($ipaDummyPass);
+
+ // grab user info from LDAP
+ $userParts = explode('@', $remoteUser);
+ $userInfo = LibreTime_Model_FreeIpa::GetUserInfo($userParts[0]);
+
+ $user->setType($userInfo['type']);
+ $user->setFirstName($userInfo['first_name']);
+ $user->setLastName($userInfo['last_name']);
+ $user->setEmail($userInfo['email']);
+ $user->setCellPhone($userInfo['cell_phone']);
+ $user->setSkype($userInfo['skype']);
+ $user->setJabber($userInfo['jabber']);
+ $user->save();
+ $this->user = $user;
+ try {
+ return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $user);
+ } catch (Exception $e) {
+ // exception occured
+ return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null);
+ }
+ }
+
+ /**
+ * return dummy object for internal auth handling
+ *
+ * we need to build a dummpy object since the auth layer knows nothing about the db
+ *
+ * @return stdClass
+ */
+ public function getResultRowObject() {
+ $o = new \stdClass;
+ $o->id = $this->user->getId();
+ $o->username = $this->user->getLogin();
+ $o->password = $this->user->getPassword();
+ $o->real_name = implode(' ', array($this->user->getFirstName(), $this->user->getLastName()));
+ $o->type = $this->user->getType();
+ $o->login = $this->user->getLogin();
+ return $o;
+ }
+}
diff --git a/airtime_mvc/application/common/GoogleAnalytics.php b/airtime_mvc/application/common/GoogleAnalytics.php
index 2a8d0184b..77a36f44a 100644
--- a/airtime_mvc/application/common/GoogleAnalytics.php
+++ b/airtime_mvc/application/common/GoogleAnalytics.php
@@ -76,6 +76,10 @@ class Application_Common_GoogleAnalytics
/** Return true if the user used to be on a trial plan and was just converted to a paid plan. */
public static function didPaidConversionOccur($request)
{
+ if (LIBRETIME_ENABLE_GOOGLE_ANALYTICS !== true) {
+ return false;
+ }
+
$userInfo = Zend_Auth::getInstance()->getStorage()->read();
if ($userInfo) {
$user = new Application_Model_User($userInfo->id);
diff --git a/airtime_mvc/application/configs/conf.php b/airtime_mvc/application/configs/conf.php
index 0a0339047..d51be7f50 100644
--- a/airtime_mvc/application/configs/conf.php
+++ b/airtime_mvc/application/configs/conf.php
@@ -37,6 +37,11 @@ class Config {
$CC_CONFIG['dev_env'] = 'production';
}
+ $CC_CONFIG['auth'] = 'local';
+ if (isset($values['general']['auth'])) {
+ $CC_CONFIG['auth'] = $values['general']['auth'];
+ }
+
//Backported static_base_dir default value into saas for now.
if (array_key_exists('static_base_dir', $values['general'])) {
$CC_CONFIG['staticBaseDir'] = $values['general']['static_base_dir'];
@@ -64,7 +69,7 @@ class Config {
$CC_CONFIG['cache_ahead_hours'] = $values['general']['cache_ahead_hours'];
- // Database config
+ // Database config
$CC_CONFIG['dsn']['username'] = $values['database']['dbuser'];
$CC_CONFIG['dsn']['password'] = $values['database']['dbpass'];
$CC_CONFIG['dsn']['hostspec'] = $values['database']['host'];
@@ -92,6 +97,19 @@ class Config {
$CC_CONFIG['facebook-app-api-key'] = $globalAirtimeConfigValues['facebook']['facebook_app_api_key'];
}
+ // ldap config
+ $CC_CONFIG['ldap_hostname'] = $values['ldap']['hostname'];
+ $CC_CONFIG['ldap_binddn'] = $values['ldap']['binddn'];
+ $CC_CONFIG['ldap_password'] = $values['ldap']['password'];
+ $CC_CONFIG['ldap_account_domain'] = $values['ldap']['account_domain'];
+ $CC_CONFIG['ldap_basedn'] = $values['ldap']['basedn'];
+ $CC_CONFIG['ldap_groupmap_guest'] = $values['ldap']['groupmap_guest'];
+ $CC_CONFIG['ldap_groupmap_host'] = $values['ldap']['groupmap_host'];
+ $CC_CONFIG['ldap_groupmap_program_manager'] = $values['ldap']['groupmap_program_manager'];
+ $CC_CONFIG['ldap_groupmap_admin'] = $values['ldap']['groupmap_admin'];
+ $CC_CONFIG['ldap_groupmap_superadmin'] = $values['ldap']['groupmap_superadmin'];
+ $CC_CONFIG['ldap_filter_field'] = $values['ldap']['filter_field'];
+
if(isset($values['demo']['demo'])){
$CC_CONFIG['demo'] = $values['demo']['demo'];
}
diff --git a/airtime_mvc/application/models/Auth.php b/airtime_mvc/application/models/Auth.php
index 03bd7f48b..7fc9b1ea5 100644
--- a/airtime_mvc/application/models/Auth.php
+++ b/airtime_mvc/application/models/Auth.php
@@ -76,7 +76,10 @@ class Application_Model_Auth
public static function getAuthAdapter()
{
$CC_CONFIG = Config::getConfig();
-
+ if ($CC_CONFIG['auth'] !== 'local') {
+ return self::getCustomAuthAdapter($CC_CONFIG['auth']);
+ }
+
// Database config
$db = Zend_Db::factory('PDO_' . $CC_CONFIG['dsn']['phptype'], array(
'host' => $CC_CONFIG['dsn']['hostspec'],
@@ -95,6 +98,15 @@ class Application_Model_Auth
return $authAdapter;
}
+ /**
+ * Gets an alternative Adapter that does not need to auth agains a databse table
+ *
+ * @return object
+ */
+ public static function getCustomAuthAdapter($adaptor) {
+ return new $adaptor();
+ }
+
/**
* Get random string
*
diff --git a/airtime_mvc/application/models/FreeIpa.php b/airtime_mvc/application/models/FreeIpa.php
new file mode 100644
index 000000000..01937cb48
--- /dev/null
+++ b/airtime_mvc/application/models/FreeIpa.php
@@ -0,0 +1,74 @@
+search(sprintf('%s=%s', $config['ldap_filter_field'], $username, $config['ldap_basedn']));
+
+ if ($ldapResults->count() !== 1) {
+ throw new Exception('Could not find logged user in LDAP');
+ }
+ $ldapUser = $ldapResults->getFirst();
+
+ $groupMap = array(
+ UTYPE_GUEST => $config['ldap_groupmap_guest'],
+ UTYPE_HOST => $config['ldap_groupmap_host'],
+ UTYPE_PROGRAM_MANAGER => $config['ldap_groupmap_program_manager'],
+ UTYPE_ADMIN => $config['ldap_groupmap_admin'],
+ UTYPE_SUPERADMIN => $config['ldap_groupmap_superadmin'],
+ );
+ $type = UTYPE_GUEST;
+ foreach ($groupMap as $groupType => $group) {
+ if (in_array($group, $ldapUser['memberof'])) {
+ $type = $groupType;
+ }
+ }
+
+ // grab first value for multivalue field
+ $firstName = $ldapUser['givenname'][0];
+ $lastName = $ldapUser['sn'][0];
+ $mail = $ldapUser['mail'][0];
+
+ // return full user info for auth adapter
+ return array(
+ 'type' => $type,
+ 'first_name' => $firstName,
+ 'last_name' => $lastName,
+ 'email' => $mail,
+ 'cell_phone' => '', # empty since I did not find it in ldap
+ 'skype' => '', # empty until we decide on a field
+ 'jabber' => '' # empty until we decide on a field
+ );
+ }
+
+ /**
+ * Bind to ldap so we can fetch additional user info
+ *
+ * @return Zend_Ldap
+ */
+ private static function _getLdapConnection()
+ {
+ $config = Config::getConfig();
+
+ $options = array(
+ 'host' => $config['ldap_hostname'],
+ 'username' => $config['ldap_binddn'],
+ 'password' => $config['ldap_password'],
+ 'bindRequiresDn' => true,
+ 'accountDomainName' => $config['ldap_account_domain'],
+ 'baseDn' => $config['ldap_basedn']
+ );
+ $conn = new Zend_Ldap($options);
+ $conn->connect();
+ return $conn;
+ }
+}
diff --git a/airtime_mvc/build/airtime.example.conf b/airtime_mvc/build/airtime.example.conf
index 7dc2f2cd2..a4459338b 100644
--- a/airtime_mvc/build/airtime.example.conf
+++ b/airtime_mvc/build/airtime.example.conf
@@ -42,6 +42,11 @@
# station_id: The Airtime station name.
# Only used in saas, needed for compatibility.
#
+# auth: Auth adaptor to user
+# Set to local to use the default db auth or specifiy
+# a class like LibreTime_Auth_Adaptor_FreeIpa to replace
+# the built-in adaptor
+#
[general]
api_key =
web_server_user = www-data
@@ -51,6 +56,7 @@ base_dir = /
cache_ahead_hours = 1
airtime_dir =
station_id =
+auth = local
#
# ----------------------------------------------------------------------
@@ -308,3 +314,38 @@ soundcloud_redirect_uri = http://libretime.example.org/soundcloud_callback.php
facebook_app_id = 0
facebook_app_url = http://example.org
facebook_app_api_key = 0
+
+#
+# ----------------------------------------------------------------------
+# L D A P
+# ----------------------------------------------------------------------
+#
+# hostname: Hostname of LDAP server
+#
+# binddn: Complete DN of user used to bind to LDAP
+#
+# password: Password for binddn user
+#
+# account_domain: Domain part of username
+#
+# basedn: base search DN
+#
+# filter_field: Name of the uid field for searching
+# Usually uid, may be cn
+#
+# groupmap_*: Map LibreTime user types to LDAP groups
+# Lets LibreTime assign user types based on the
+# group a given user is in.
+#
+[ldap]
+hostname = ldap.example.org
+binddn = 'uid=libretime,cn=sysaccounts,cn=etc,dc=int,dc=example,dc=org'
+password = hackme
+account_domain = INT.EXAMPLE.ORG
+basedn = 'cn=users,cn=accounts,dc=int,dc=example,dc=org'
+filter_field = uid
+groupmap_guest = 'cn=guest,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_host = 'cn=host,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_program_manager = 'cn=program_manager,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_admin = 'cn=admins,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_superadmin = 'cn=superadmin,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
diff --git a/docs/freeipa.md b/docs/freeipa.md
new file mode 100644
index 000000000..837ef620d
--- /dev/null
+++ b/docs/freeipa.md
@@ -0,0 +1,102 @@
+You can configure LibreTime to delegate all authentication to a FreeIPA server.
+
+This allows you users to use their existing FreeIPA credentials. For this to
+work you need to configure Apache to use `mod_authnz_pam` and `mod_intercept_form_submit`.
+
+## Apache configuration
+
+After installing the needed modules you can set up Apache to intercept form logins and
+check them against pam.
+
+```apache
+
+ InterceptFormPAMService http-libretime
+ InterceptFormLogin username
+ InterceptFormPassword password
+ InterceptFormLoginSkip admin
+ InterceptFormPasswordRedact on
+ InterceptFormLoginRealms INT.RABE.CH
+ Require pam-account http-libretime
+
+
+
+
+
+ Require pam-account http-libretime
+ Require all granted
+
+
+ Require expr %{REQUEST_URI} =~ /(index.php|login|favicon.ico|js|css|locale)/
+ Require all granted
+
+
+
+```
+
+## PAM configuration
+
+The above configuration expects a PAM configuration for the `http-libretime` service.
+
+To confiure this you need to create the file `/etc/pam.d/http-libretime` with the following contents.
+
+```
+auth required pam_sss.so
+account required pam_sss.so
+```
+
+## LDAP configuration
+
+LibreTime needs direct access to LDAP so it can fetch additional information. It does so with
+a [system account](https://www.freeipa.org/page/HowTo/LDAP#System_Accounts) that you need to
+set up beforehand.
+
+You can configure everything pertaining to how LibreTime accesses LDAP in
+`/etc/airtime/airtime.conf`. The default file has the following values you need to change.
+
+```ini
+#
+# ----------------------------------------------------------------------
+# L D A P
+# ----------------------------------------------------------------------
+#
+# hostname: Hostname of LDAP server
+#
+# binddn: Complete DN of user used to bind to LDAP
+#
+# password: Password for binddn user
+#
+# account_domain: Domain part of username
+#
+# basedn: base search DN
+#
+# filter_field: Name of the uid field for searching
+# Usually uid, may be cn
+#
+# groupmap_*: Map LibreTime user types to LDAP groups
+# Lets LibreTime assign user types based on the
+# group a given user is in.
+#
+[ldap]
+hostname = ldap.example.org
+binddn = 'uid=libretime,cn=sysaccounts,cn=etc,dc=int,dc=example,dc=org'
+password = hackme
+account_domain = INT.EXAMPLE.ORG
+basedn = 'cn=users,cn=accounts,dc=int,dc=example,dc=org'
+filter_field = uid
+groupmap_guest = 'cn=guest,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_host = 'cn=host,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_program_manager = 'cn=program_manager,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_admin = 'cn=admins,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+groupmap_superadmin = 'cn=superadmin,cn=groups,cn=accounts,dc=int,dc=example,dc=org'
+```
+
+## Enable FreeIPA auth
+
+After everything is set up properly you can enable FreeIPA auth in `airtime.conf`:
+
+```
+[general]
+auth = LibreTime_Auth_Adaptor_FreeIpa
+```
+
+You should now be able to use your FreeIPA credentials to log in to LibreTime.
diff --git a/mkdocs.yml b/mkdocs.yml
index cec423ee4..5ffdb54f0 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -69,6 +69,7 @@ pages:
- 'LibreTime API authentication': manual/airtime-api-authentication/index.md
- 'Secure login with SSL or TLS': manual/secure-login-with-ssl/index.md
- 'Icecast statistics with Piwik': manual/icecast-statistics-with-piwik/index.md
+ - 'FreeIPA Authentication': freeipa.md
- 'Development':
- 'Testing': testing.md
- 'Vagrant': vagrant.md