from __future__ import print_function
from builtins import range
from builtins import object
import string
import random
import logging
import requests
from zope import interface
from pyramid.settings import asbool
from assembl.lib import config
from assembl.lib.discussion_creation import IDiscussionCreationCallback
log = logging.getLogger(__name__)
[docs]@interface.implementer(IDiscussionCreationCallback)
class AutomaticPiwikBindingAtDiscussionCreation(object):
"""A :py:class:`IDiscussionCreationCallback` that creates a Piwik site and user at discussion creation"""
def discussionCreated(self, discussion):
bind_piwik(discussion)
def bind_piwik(discussion, admin=None):
admin = admin or discussion.creator
assert admin, "There should be a discussion admin provided"
user_email = admin.get_preferred_email()
assert user_email, "the admin should have an email"
piwik_url = config.get('web_analytics_piwik_url')
piwik_api_token = config.get('web_analytics_piwik_api_token')
missing_variables = []
if not user_email:
missing_variables.append("user_email")
if not piwik_url:
missing_variables.append("piwik_url")
if not piwik_api_token:
missing_variables.append("piwik_api_token")
if len(missing_variables):
raise RuntimeError("missing configuration variables: " + ", ".join(missing_variables))
# TODO: Should this process first check that discussion.web_analytics_piwik_id_site is empty and do something different if it's not? (for example: return an error, so that we empeach discussion statistics to be scattered on different Piwik sites, which is difficult to merge)
try:
# Check wether a Piwik user with a `user_email` email exists
try:
user_already_exists = piwik_UsersManager_getUserByEmail(piwik_url, piwik_api_token, user_email)
except requests.ConnectionError:
raise RuntimeError("call to Piwik returned an error (piwik_UsersManager_getUserByEmail)")
user_created = False
user_password = ""
user_login = user_email
if not user_already_exists:
# Create a Piwik user with `user_email` as login and as email
user_password = string_generator(size=10)
user_created = piwik_UsersManager_addUser(piwik_url, piwik_api_token, user_email, user_password, user_email)
if not user_created:
# Try to find if creation failed because of rare/edge case of a Piwik user already existing with the user_email as login but not as email
log.error("##### user not created, trying to find why")
user_with_email_as_login_exists = piwik_UsersManager_userExists(piwik_url, piwik_api_token, user_email)
if user_with_email_as_login_exists:
# We will use this strange Piwik user
user_login = user_email
log.error("##### we are in the rare case of a Piwik user already existing with the user_email as login but not as email")
else:
raise requests.ConnectionError()
else:
user_login = user_already_exists[0]["login"]
# Check wether a Piwik site with this URL already exists
discussion_urls = discussion.get_discussion_urls()
site_url = discussion_urls[0]
sites_ids_with_this_url = piwik_SitesManager_getSitesIdFromSiteUrl(piwik_url, piwik_api_token, site_url)
site_id = None
site_already_exists = len(sites_ids_with_this_url) > 0
if len(sites_ids_with_this_url) == 1:
site_id = sites_ids_with_this_url[0]["idsite"]
elif len(sites_ids_with_this_url) == 0:
pass # OK. No Piwik site exist yet with this URL, so we will create it.
else:
# Here, instead of returning an error, we could just use the first site returned. But we would have to make sure that there is no tracking problems afterwards. Having several id_site pointing to the same URLs is not a good situation I guess.
raise RuntimeError("Several Piwik websites already exist with this URL. There should be only 0 or 1. Please log into Piwik as Super User and remove duplicate sites.")
if not site_already_exists:
# create a Piwik website
site_name = discussion.slug
# TODO: Some parameters here should probably be variables received from somewhere, because they could be different from one platform instance to another, or from one discussion to another, like timezone for example
addsite_result = piwik_SitesManager_addSite(piwik_url, piwik_api_token, site_name, discussion_urls, timezone="Europe/Paris", currency="EUR")
if addsite_result is not False and isinstance(addsite_result, int):
site_id = addsite_result
else:
raise RuntimeError("could not create Piwik site")
if site_id:
# Give "view" permission to Piwik user on Piwik site
permission_given = piwik_UsersManager_setUserAccess(piwik_url, piwik_api_token, user_login, "view", [site_id])
if not permission_given:
# Handle corner case of Piwik response being {"result":"error","message":"This user has Super User access and has already permission to access and modify all websites in Piwik. You may remove the Super User access from this user and try again."}
user_has_super_user_access = piwik_UsersManager_hasSuperUserAccess(piwik_url, piwik_api_token, user_login)
if user_has_super_user_access:
permission_given = True
log.error("##### This Piwik user exists and is Super User, so he has access to any Piwik site")
else:
raise RuntimeError("Could not give view permission to Piwik user on Piwik site (and user does not seem to have Super User access)")
# Set discussion's piwik id_site property
discussion.web_analytics_piwik_id_site = site_id
res = {
"user_already_exists": user_already_exists,
"user_created": user_created,
"site_already_exists": site_already_exists,
"result": "success"
}
if user_created:
res["user_password"] = user_password
if site_id:
res["site_id"] = site_id
return res
except requests.ConnectionError as e:
raise RuntimeError("call to Piwik returned an error")
def piwik_UsersManager_userExists(piwik_url, piwik_api_token, userLogin):
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "UsersManager.userExists"
params["userLogin"] = userLogin # Piwik has two different fields for login and email, but a user can have the same value as login and email
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json()
# log.info( "piwik_UsersManager_userExists", content)
if not content:
raise requests.ConnectionError()
user_already_exists = asbool(content.get("value", False))
return user_already_exists
def piwik_UsersManager_getUserByEmail(piwik_url, piwik_api_token, userEmail):
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "UsersManager.getUserByEmail"
params["userEmail"] = userEmail # Piwik has two different fields for login and email, but a user can have the same value as login and email
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json() # returns something like [{"login":"aaa","email":"aaa@aaa.com"}] or {"result":"error","message":"L'utilisateur 'aaa@aaa.com' est inexistant."}
# log.info( "piwik_UsersManager_getUserByEmail", content)
if not content:
raise requests.ConnectionError()
if "result" in content and content["result"] == "error":
return False
elif not isinstance(content, list):
raise requests.ConnectionError()
else:
return content
def piwik_UsersManager_addUser(piwik_url, piwik_api_token, userLogin, password, email, alias=''):
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "UsersManager.addUser"
params["userLogin"] = userLogin # Piwik has two different fields for login and email, but a user can have the same value as login and email
params["password"] = password
params["email"] = email
if ( alias ):
params["alias"] = alias
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json()
# log.debug( "piwik_UsersManager_addUser", content)
if not content:
raise requests.ConnectionError()
user_added = ("result" in content and content["result"] == "success")
return user_added
def piwik_SitesManager_getSitesIdFromSiteUrl(piwik_url, piwik_api_token, url):
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "SitesManager.getSitesIdFromSiteUrl"
params["url"] = url
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json() # Content should be either an empty array, or an array like [{"idsite":"44"}]
# log.debug( "piwik_SitesManager_getSitesIdFromSiteUrl", content)
if not isinstance(content, list):
raise requests.ConnectionError()
return content
[docs]def piwik_SitesManager_addSite(piwik_url, piwik_api_token, siteName, urls, ecommerce = '', siteSearch = '', searchKeywordParameters = '', searchCategoryParameters = '', excludedIps = '', excludedQueryParameters = '', timezone = '', currency = '', group = '', startDate = '', excludedUserAgents = '', keepURLFragments = '', param_type = '', settings = '', excludeUnknownUrls = ''):
"""Returns the Piwik id_site of the created website
Warning: A new Piwik site is created everytime this method is called, even if another Piwik website already exists with the same name and URLs"""
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "SitesManager.addSite"
params["siteName"] = siteName
params["urls"] = urls
if ( ecommerce ):
params["ecommerce"] = ecommerce
if ( siteSearch ):
params["siteSearch"] = siteSearch
if ( searchKeywordParameters ):
params["searchKeywordParameters"] = searchKeywordParameters
if ( searchCategoryParameters ):
params["searchCategoryParameters"] = searchCategoryParameters
if ( excludedIps ):
params["excludedIps"] = excludedIps
if ( excludedQueryParameters ):
params["excludedQueryParameters"] = excludedQueryParameters
if ( timezone ):
params["timezone"] = timezone
if ( currency ):
params["currency"] = currency
if ( group ):
params["group"] = group
if ( startDate ):
params["startDate"] = startDate
if ( excludedUserAgents ):
params["excludedUserAgents"] = excludedUserAgents
if ( keepURLFragments ):
params["keepURLFragments"] = keepURLFragments
if ( param_type ):
params["type"] = param_type
if ( settings ):
params["settings"] = settings
if ( excludeUnknownUrls ):
params["excludeUnknownUrls"] = excludeUnknownUrls
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json() # Content should be something like {"value": 47}
# log.debug( "piwik_SitesManager_addSite", content)
if not content:
raise requests.ConnectionError()
if "value" in content:
return content['value']
return False
def piwik_UsersManager_setUserAccess(piwik_url, piwik_api_token, userLogin, access, idSites):
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "UsersManager.setUserAccess"
params["userLogin"] = userLogin
params["access"] = access
params["idSites"] = idSites
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json() # Content should be either an empty array, or an array like [{"idsite":"44"}]
# log.debug( "piwik_UsersManager_setUserAccess", content)
if not content:
raise requests.ConnectionError()
user_access_is_set = ("result" in content and content["result"] == "success")
return user_access_is_set
def piwik_UsersManager_hasSuperUserAccess(piwik_url, piwik_api_token, userLogin):
params = {
"module": "API",
"format": "JSON",
"token_auth": piwik_api_token
}
params["method"] = "UsersManager.hasSuperUserAccess"
params["userLogin"] = userLogin
result = requests.get(piwik_url, params=params, timeout=15)
if result.status_code != 200:
raise requests.ConnectionError()
content = result.json() # Content should be like {"value": true}
# log.debug( "piwik_UsersManager_hasSuperUserAccess", content)
if not content:
raise requests.ConnectionError()
return asbool(content.get("value", False))
def string_generator(size=10, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))