OSDN Git Service

BugTrack/2411 SAML plugin: PukiWiki as a SAML service provider
authorumorigu <umorigu@gmail.com>
Sun, 9 Jul 2017 06:25:27 +0000 (15:25 +0900)
committerumorigu <umorigu@gmail.com>
Sun, 9 Jul 2017 14:25:21 +0000 (14:25 +0000)
* Add SAML plugin that enable SAML authentication
* Parse '?//key1.value2//key2.value2' type querystring
  that we handles as query parameters
* The SAML plugin depends on onlogin/php-saml library.

How to use SAML plugin:

* install onelogin/php-saml `composer install onlogin/php-saml`
* Create saml_settings.php
* Set $auth_type = AUTH_TYPE_SAML; in pukiwiki.ini.php

lib/auth.php
lib/html.php
lib/init.php
plugin/loginform.inc.php
plugin/saml.inc.php [new file with mode: 0644]
pukiwiki.ini.php

index ad0b880..139af6c 100644 (file)
@@ -18,6 +18,7 @@ define('AUTH_TYPE_FORM', 3);
 
 define('AUTH_TYPE_EXTERNAL_REMOTE_USER', 4);
 define('AUTH_TYPE_EXTERNAL_X_FORWARDED_USER', 5);
+define('AUTH_TYPE_SAML', 6);
 
 
 // Passwd-auth related ----
@@ -312,7 +313,8 @@ function basic_auth($page, $auth_enabled, $exit_on_fail, $auth_pages, $title_can
                                        . '&url_after_login=' . rawurlencode($url_after_login);
                                header('HTTP/1.0 302 Found');
                                header('Location: ' . $loginurl);
-                       } elseif (AUTH_TYPE_EXTERNAL === $auth_type) {
+                       } elseif (AUTH_TYPE_EXTERNAL === $auth_type ||
+                               AUTH_TYPE_SAML === $auth_type) {
                                $url_after_login = get_script_uri() . '?' . $g_query_string;
                                $loginurl = get_auth_external_login_url($page, $url_after_login);
                                header('HTTP/1.0 302 Found');
@@ -348,6 +350,7 @@ function ensure_valid_auth_user()
                        case AUTH_TYPE_EXTERNAL:
                        case AUTH_TYPE_EXTERNAL_REMOTE_USER:
                        case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
+                       case AUTH_TYPE_SAML:
                                break;
                        default:
                                // $auth_type is not valid, Set form auth as default
@@ -379,6 +382,7 @@ function ensure_valid_auth_user()
                }
                case AUTH_TYPE_FORM:
                case AUTH_TYPE_EXTERNAL:
+               case AUTH_TYPE_SAML:
                {
                        session_start();
                        $user = '';
@@ -391,7 +395,8 @@ function ensure_valid_auth_user()
                                        $dynamic_groups = $_SESSION['dynamic_member_groups'];
                                } else {
                                        $fullname = $user;
-                                       if ($auth_type === AUTH_TYPE_EXTERNAL && $ldap_user_account) {
+                                       if (($auth_type === AUTH_TYPE_EXTERNAL || $auth_type === AUTH_TYPE_SAML) &&
+                                               $ldap_user_account) {
                                                $ldap_user_info = ldap_get_simple_user_info($user);
                                                if ($ldap_user_info) {
                                                        $fullname = $ldap_user_info['fullname'];
@@ -649,6 +654,7 @@ function get_auth_user_prefix() {
        global $auth_provider_user_prefix_default;
        global $auth_provider_user_prefix_ldap;
        global $auth_provider_user_prefix_external;
+       global $auth_provider_user_prefix_saml;
        $user_prefix = '';
        switch ($auth_type) {
                case AUTH_TYPE_BASIC:
@@ -659,6 +665,9 @@ function get_auth_user_prefix() {
                case AUTH_TYPE_EXTERNAL_X_FORWARDED_USER:
                        $user_prefix = $auth_provider_user_prefix_external;
                        break;
+               case AUTH_TYPE_SAML:
+                       $user_prefix = $auth_provider_user_prefix_saml;
+                       break;
                case AUTH_TYPE_FORM:
                        if ($ldap_user_account) {
                                $user_prefix = $auth_provider_user_prefix_ldap;
index a85908a..1d9e42b 100644 (file)
@@ -26,7 +26,8 @@ function catbody($title, $page, $body)
 
        $enable_login = false;
        $enable_logout = false;
-       if (AUTH_TYPE_FORM === $auth_type || AUTH_TYPE_EXTERNAL === $auth_type) {
+       if (AUTH_TYPE_FORM === $auth_type || AUTH_TYPE_EXTERNAL === $auth_type ||
+               AUTH_TYPE_SAML === $auth_type) {
                if ($auth_user) {
                        $enable_logout = true;
                } else {
@@ -79,6 +80,7 @@ function catbody($title, $page, $body)
                        $login_link = "$script?plugin=loginform&pcmd=login&page=$r_page";
                        break;
                case AUTH_TYPE_EXTERNAL:
+               case AUTH_TYPE_SAML:
                        $login_link = get_auth_external_login_url($_page, $_LINK['reload']);
                        break;
        }
index f395e6e..bbffb24 100644 (file)
@@ -139,6 +139,12 @@ if (isset($script)) {
        $script = get_script_uri(); // Init automatically
 }
 
+// INI_FILE: Auth settings
+if (isset($auth_type) && $auth_type === AUTH_TYPE_SAML) {
+       $auth_external_login_url_base = get_script_uri() . '?//cmd.saml//sso';
+}
+
+
 /////////////////////////////////////////////////
 // INI_FILE: $agents:  UserAgentの識別
 
@@ -329,6 +335,36 @@ if (empty($_POST)) {
        $vars = array_merge($_GET, $_POST); // Considered reliable than $_REQUEST
 }
 
+/**
+ * Parse specified format query_string as params.
+ *
+ * For example: ?//key1.value2//key2.value2
+ */
+function parse_query_string_ext($query_string) {
+       $vars = array();
+       $m = null;
+       if (preg_match('#^//[^&]*#', $query_string, $m)) {
+               foreach (explode('//', $m[0]) as $item) {
+                       $sp = explode('.', $item, 2);
+                       if (isset($sp[0])) {
+                               if (isset($sp[1])) {
+                                       $vars[$sp[0]] = $sp[1];
+                               } else {
+                                       $vars[$sp[0]] = '';
+                               }
+                       }
+               }
+       }
+       return $vars;
+}
+if (isset($g_query_string) && $g_query_string) {
+       if (substr($g_query_string, 0, 2) === '//') {
+               // Parse ?//key.value//key.value format query string
+               $vars = array_merge($vars, parse_query_string_ext($g_query_string));
+       }
+}
+
+
 // 入力チェック: 'cmd=' and 'plugin=' can't live together
 if (isset($vars['cmd']) && isset($vars['plugin']))
        die('Using both cmd= and plugin= is not allowed');
index 4885436..ea7d932 100644 (file)
@@ -48,6 +48,7 @@ function plugin_loginform_action()
                                break;
                        case AUTH_TYPE_FORM:
                        case AUTH_TYPE_EXTERNAL:
+                       case AUTH_TYPE_SAML:
                        default:
                                $_SESSION = array();
                                session_regenerate_id(true); // require: PHP5.1+
diff --git a/plugin/saml.inc.php b/plugin/saml.inc.php
new file mode 100644 (file)
index 0000000..cc313c0
--- /dev/null
@@ -0,0 +1,114 @@
+<?php
+// PukiWiki - Yet another WikiWikiWeb clone.
+// saml.inc.php
+// Copyright
+//   2017 PukiWiki Development Team
+// License: GPL v2 or (at your option) any later version
+//
+// PukiWiki SAML Plugin
+
+require 'vendor/autoload.php';
+require_once 'vendor/onelogin/php-saml/_toolkit_loader.php';
+
+define('PLUGIN_SAML_AUTHUSER_ID_ATTR', 'UserId');
+define('PLUGIN_SAML_AUTHUSER_DISPLAYNAME_ATTR', 'DisplayName');
+
+/**
+ *  SAML Handler
+ */
+function plugin_saml_action() {
+       global $vars;
+       require 'saml_settings.php';
+
+       $auth = new OneLogin_Saml2_Auth($settingsInfo);
+
+       if (isset($vars['sso'])) {
+               // sso: Sign in endpoint before IdP
+               $url_after_login = $vars['url_after_login'];
+               $auth->login($url_after_login);
+       } else if (isset($vars['slo'])) {
+               // sso: Sign out endpoint before IdP
+               $returnTo = null;
+               $paramters = array();
+               $nameId = null;
+               $sessionIndex = null;
+               if (isset($_SESSION['samlNameId'])) {
+                       $nameId = $_SESSION['samlNameId'];
+               }
+               if (isset($_SESSION['samlSessionIndex'])) {
+                       $sessionIndex = $_SESSION['samlSessionIndex'];
+               }
+               $auth->logout($returnTo, $paramters, $nameId, $sessionIndex);
+       } else if (isset($vars['acs'])) {
+               // acs: Sign in endpoint after IdP
+               $auth->processResponse();
+
+               $errors = $auth->getErrors();
+
+               if (!empty($errors)) {
+                       return array('msg' => 'SAML Error', print_r('<p>'.implode(', ', $errors).'</p>'));
+               }
+
+               if (!$auth->isAuthenticated()) {
+                       return array('msg' => 'SAML sign in', 'body' => '<p>Not authenticated</p>');
+               }
+               $attrs = $auth->getAttributes();
+               $_SESSION['samlUserdata'] = $attrs;
+               $_SESSION['samlNameId'] = $auth->getNameId();
+               $_SESSION['samlSessionIndex'] = $auth->getSessionIndex();
+               if (isset($attrs[PLUGIN_SAML_AUTHUSER_ID_ATTR][0])) {
+                       // PukiWiki ExternalAuth requirement
+                       $_SESSION['authenticated_user'] = $attrs[PLUGIN_SAML_AUTHUSER_ID_ATTR][0];
+               } else {
+                       $_SESSION['authenticated_user'] = $auth->getNameId();
+               }
+               if (isset($attrs[PLUGIN_SAML_AUTHUSER_DISPLAYNAME_ATTR][0])) {
+                       // PukiWiki ExternalAuth requirement
+                       $_SESSION['authenticated_user_fullname'] = $attrs[PLUGIN_SAML_AUTHUSER_DISPLAYNAME_ATTR][0];
+               }
+
+               if (isset($_POST['RelayState']) && OneLogin_Saml2_Utils::getSelfURL() != $_POST['RelayState']) {
+                       $auth->redirectTo($_POST['RelayState']);
+               }
+               return array('msg' => 'SAML sign in', 'body' => 'SAML Sined in. but no redirection');
+       } else if (isset($vars['sls'])) {
+               // sls: Sign out endpoint after IdP
+               // onelone/php-saml only supports Redirect SingleLogout
+               $is_post = $_SERVER['REQUEST_METHOD'] === 'POST';
+               if ($is_post) {
+                       session_destroy();
+                       $_SESSION = array();
+               } else {
+                       $auth->processSLO();
+                       $errors = $auth->getErrors();
+                       $msg = '';
+                       if (empty($errors)) {
+                               $msg .= '<p>Sucessfully logged out</p>';
+                       } else {
+                               $msg .= '<p>'.implode(', ', $errors).'</p>';
+                       }
+               }
+               return array('msg' => 'SAML sign out', 'body' => 'SAML Sined out. ' . $msg);
+       } else if (isset($vars['metadata'])) {
+               // metadata: SP metadata endpoint
+               try {
+                       $auth = new OneLogin_Saml2_Auth($settingsInfo);
+                       $settings = $auth->getSettings();
+                       $metadata = $settings->getSPMetadata();
+                       $errors = $settings->validateMetadata($metadata);
+                       if (empty($errors)) {
+                               header('Content-Type: text/xml');
+                               echo $metadata;
+                       } else {
+                               throw new OneLogin_Saml2_Error(
+                                       'Invalid SP metadata: '.implode(', ', $errors),
+                                       OneLogin_Saml2_Error::METADATA_SP_INVALID
+                               );
+                       }
+               } catch (Exception $e) {
+                       echo $e->getMessage();
+               }
+               exit;
+       }
+       return array('msg' => 'Error', 'body' => 'SAML Invalid state srror');
+}
index 8f7cefb..315247e 100644 (file)
@@ -235,6 +235,7 @@ $ldap_user_account = 0; // (0: Disabled, 1: Enabled)
 $auth_provider_user_prefix_default = 'default:';
 $auth_provider_user_prefix_ldap = 'ldap:';
 $auth_provider_user_prefix_external = 'external:';
+$auth_provider_user_prefix_saml = 'saml:';
 
 
 /////////////////////////////////////////////////