Skip to content

Commit

Permalink
Initial tree submission
Browse files Browse the repository at this point in the history
  • Loading branch information
Joshua Brule committed Feb 12, 2019
1 parent b3b710a commit 866fc99
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 0 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Changelog

3.0.0 Initial Version
53 changes: 53 additions & 0 deletions Controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/

namespace Piwik\Plugins\SiteAccessProvisioner;

use Piwik\Piwik;
use Piwik\Nonce;
use Piwik\Notification;
use Piwik\Common;
use Piwik\Option;
use Piwik\Settings\Storage;
use Piwik\Settings\Setting;
use Piwik\Plugin;
use Piwik\Site;
use Piwik\Url;
use Piwik\View;

class Controller extends \Piwik\Plugin\Controller
{
const PLUGIN_NAME = "SiteAccessProvisioner";

public function index()
{

}

public function accessRequest()
{
Piwik::checkUserIsNotAnonymous();

$idUser = trim(Common::getRequestVar('idUser', '', 'string',$_GET));
$site = Common::getRequestVar('site', 0, 'string',$_GET);
$timestamp = Common::getRequestVar('timestamp', 0, 'int',$_GET);
$token = Common::getRequestVar('token', 0, 'string',$_GET);

list($msg, $idSite) = SiteAccessProvisioner::processRequest($idUser,$site,$timestamp,$token);

if(!empty($msg))
{
$notification = new Notification($msg["message"]);
$notification->context = ($msg["status"] === "success")? Notification::CONTEXT_SUCCESS : Notification::CONTEXT_ERROR;
$notification->type = Notification::TYPE_TOAST;
Notification\Manager::notify(self::PLUGIN_NAME."Notice", $notification);
}

$this->redirectToIndex('CoreHome', 'index', $idSite);
}
}
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Matomo SiteAccessProvisioner Plugin

##Description
Plugin for the Matomo Web Analytics software package that facilitates an easy process to grant users access to site reports. A companion access provider is required, usually in the form of a website CMS plugin/module (you may need to build this if one does not exist).

##Instructions
The easiest way to install is to find the plugin in the [Matomo Marketplace](https://plugins.matomo.org/).
A shared secret must be etablished before the plugin will function.
You will then need to implement an access provider (see example code below) which will generate an access request link users can use.

##Usage

Implementation code for access provider. Should be trivial to port to other languages.
```php
$sharedSecret = "xxxxxxxxxxxxx"; //Shared secret. This needs to match the secret set in the Plugin settings. Should only be accessible by admin.
$idUser = "myusername"; //Matomo username
$site = "www.example.com/practicesubsite"; //URL of the site to request access for. Also accepts idSite.
$timestamp = time();
$token = hash('sha256', implode('',[$sharedSecret, $idUser, $site, $timestamp]));

$linkHref = sprintf("http://matomoinstall.example.com/matomo/index.php?%s", http_build_query(["module"=>"SiteAccessProvisioner", "action"=>"accessRequest", "idUser"=>$idUser, "site"=>$site, "timestamp"=>$timestamp, "token"=>$token]));
```
##License
GPL v3 / fair use

## Support
Please [report any issues](https://github.com/jbrule/matomoplugin-SiteAccessProvisioner/issues). Pull requests welcome.
97 changes: 97 additions & 0 deletions SiteAccessProvisioner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/

namespace Piwik\Plugins\SiteAccessProvisioner;

use Piwik\Piwik;
use Piwik\Access;
use Piwik\Common;
use Piwik\Option;
use Piwik\Settings\Storage;
use Piwik\Settings\Setting;
use Piwik\Plugins\SitesManager\API as APISitesManager;
use Piwik\Plugins\UsersManager\API as APIUsersManager;

class SiteAccessProvisioner extends \Piwik\Plugin
{
public static function processRequest($idUser, $site, $timestamp, $token)
{
$settings = new SystemSettings();

$sharedSecret = $settings->sharedSecret->getValue();

if(empty($sharedSecret))
{
throw new \Exception("Plugin function is inactive. Shared secret not set.");
}

$tokenttl = $settings->tokenttl->getValue();

$enforceMatchingUserId = $settings->enforceMatchingUserId->getValue();

$msg = array();

$idSite = false;

$computedToken = hash('sha256',implode('',[$sharedSecret, $idUser, $site, $timestamp]));
$currentTimestamp = time();

if($token === $computedToken && (!$enforceMatchingUserId || ($enforceMatchingUserId && strtolower($idUser) == strtolower(Piwik::getCurrentUserLogin()))))
{
if(($currentTimestamp - $timestamp) <= $tokenttl)
{
$idSite = (is_int($site))? $site : self::getIdSiteFromUrl($site);

if($idSite !== false)
{
if(!Piwik::isUserHasViewAccess($idSite) && !Piwik::isUserHasAdminAccess($idSite) && !Piwik::hasUserSuperUserAccess())
{
self::grantSiteAccess($idSite,"view");
if(Piwik::isUserHasViewAccess($idSite))
{
$msg["status"] = "success";
$msg["message"] = sprintf("You were successfully granted view access");
}
}
}else{
$msg["status"] = "error";
$msg["message"] = "Could not get idSite";
}

}else{
$msg["status"] = "error";
$msg["message"] = "Access token has expired";
}
}else{
$msg["status"] = "error";
$msg["message"] = "Invalid token";
}

return [$msg, $idSite];
}

private static function getIdSiteFromUrl($url)
{
$result = Access::doAsSuperUser(function() use ($url){
$sitesManager = APISitesManager::getInstance();
return $sitesManager->getSitesIdFromSiteUrl($url);
});

return (!empty($result))? $result[0]["idsite"] : false;
}

private static function grantSiteAccess($idSite, $accessLevel)
{
$login = Piwik::getCurrentUserLogin();

return Access::doAsSuperUser(function() use ($login, $accessLevel, $idSite){
$usersManager = APIUsersManager::getInstance();
return $usersManager->setUserAccess($login, $accessLevel, $idSite);
});
}
}
66 changes: 66 additions & 0 deletions SystemSettings.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php
/**
* Piwik - free/libre analytics platform
*
* @link http://piwik.org
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
*/

namespace Piwik\Plugins\SiteAccessProvisioner;

use Piwik\Settings\Setting;
use Piwik\Settings\FieldConfig;
use Piwik\Validators\NotEmpty;
use Piwik\Validators\CharacterLength;
use Piwik\Validators\NumberRange;

class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings
{
public $sharedSecret;

public $tokenttl;

public $enforceMatchingUserId;

protected function init()
{
$this->sharedSecret = $this->createSharedSecretSetting();

$this->tokenttl = $this->createTokenttlSetting();

$this->enforceMatchingUserId = $this->createEnforceMatchingUserIdSetting();
}

private function createSharedSecretSetting()
{
return $this->makeSetting('sharedSecret', $default = '', FieldConfig::TYPE_STRING, function (FieldConfig $field) {
$field->title = 'Shared Secret';
$field->uiControl = FieldConfig::UI_CONTROL_TEXT;
$field->description = 'Set the shared secret. This value should not be exposed to users.';
$field->inlineHelp = '<br /><strong>NOTICE:</strong> The access provider must use this same value for its secret.<br /><br /><a target="_blank" href="">How do I create an access provider?</a>';
$field->validators[] = new NotEmpty();
$field->validators[] = new CharacterLength(10,500);
});
}

private function createTokenttlSetting()
{
return $this->makeSetting('Tokenttl', $default = 120, FieldConfig::TYPE_INT, function (FieldConfig $field) {
$field->title = 'Token Time to Live';
$field->uiControl = FieldConfig::UI_CONTROL_TEXT;
$field->description = 'Time (in seconds) that the token is valid for.';
$field->inlineHelp = '<br /><strong>NOTICE:</strong> If the access provider and Matomo server clocks are not in approximate sync, tokens may be flagged as expired.<br />';
$field->validators[] = new NotEmpty();
$field->validators[] = new NumberRange(1,3600); //1hr
});
}

private function createEnforceMatchingUserIdSetting()
{
return $this->makeSetting('enforceMatchingUserId', $default = 1, FieldConfig::TYPE_BOOL, function (FieldConfig $field) {
$field->title = 'UserId must match Matomo username';
$field->uiControl = FieldConfig::UI_CONTROL_CHECKBOX;
$field->description = 'Enforce checking if the idUser param provided by the access provider matches the logged in user.';
});
}
}
16 changes: 16 additions & 0 deletions docs/faq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## FAQ

__Why are accounts not being created?__

This plugin does not create user accounts. It just authorizes already existing accounts to view site tracking reports. If automatic account creation is desired I would suggest looking at the LdapLogin plugin in the Marketplace.
You would need access to an Ldap directory for it to work however.

__We are always seeing token expired error?__

If your access provider code and Matomo are on seprate servers this could be a symptom of the clocks on either server being incorrect. Using a service such as ntpd on Linux is highly recommended. If you have full control of your server lookup how to setup ntpd for your distribution.
If you are using a hosting service and your system time is incorrect contact your hosting company to find out how to use the Network Time Protocol with your server. Timezone settings should not be a factor as we are using a UNIX TIMESTAMP for calculation.

__I built an access provider for xxxx CMS. Would you like to be informed?__

Please let me know by [reporting as an issue](https://github.com/jbrule/matomoplugin-SiteAccessProvisioner/issues). Maintaining a directory can be a demanding job so I do not have plans to maintain an access provider directory at this time.
If you create an access provider as a companion to this plugin please reference this plugin in your plugin/module documentation.
4 changes: 4 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Documentation

The easiest way to install is to find the plugin in the [Matomo Marketplace](https://plugins.matomo.org/).
After activation go to General Settings and set a SharedSecret. You will then need to implement an access provider (example code available in README) which will generate a link for users to use to access Matomo.
19 changes: 19 additions & 0 deletions plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "SiteAccessProvisioner",
"description": "Provides a simple method to provision access to site analytics. Integration with Content Management System is highly advised.",
"version": "3.0.0",
"theme": false,
"require": {
"piwik": ">=3.6.0-stable,<4.0.0-b1"
},
"authors": [
{
"name": "Josh Brule",
"email": "",
"homepage": "https:\/\/www.linkedin.com\/pub\/joshua-brule\/15\/326\/9b9"
}
],
"homepage": "https:\/\/github.com\/jbrule\/matomoplugin-SiteAccessProvisioner",
"license": "GPL v3+",
"keywords": ["access","authorization","integration"]
}
8 changes: 8 additions & 0 deletions templates/index.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% extends 'dashboard.twig' %}

{% block content %}
<strong>Hello world!</strong>
<br/>

The answer to life is {{ answerToLife }}
{% endblock %}

0 comments on commit 866fc99

Please sign in to comment.