Plone: Notify a user on deleting his account - plone

Using a subscriber on IPrincipalDeletedEvent is not a solution because the user is already deleted and I can't get his email address.
<subscriber
for="* Products.PluggableAuthService.interfaces.events.IPrincipalDeletedEvent"
handler="mycontent.userDeleted" />
https://github.com/plone/Products.PlonePAS/blob/4.2/Products/PlonePAS/pas.py#L78
api.user.get(userid=user_id) is None when my userDeleted(user_id, event) is called.
It seems adding a content rule for user removed is working the same.
Any idea how to get user's email address when his account is marked to be deleted? I just want to send him an email: Your account was deleted as you requested.

Monkey patching to add a event just before the user is deleted:
In patches.zcml:
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:monkey="http://namespaces.plone.org/monkey"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="myapp">
<include package="collective.monkeypatcher" />
<include package="collective.monkeypatcher" file="meta.zcml" />
<monkey:patch description="Add PrincipalBeforeDeleted event"
class="Products.PlonePAS.pas"
original="_doDelUser"
replacement="mycontent.patches._doDelUser"
docstringWarning="true" />
</configure>
In patches.py:
from zope.event import notify
from Products.PluggableAuthService.events import PrincipalDeleted
from Products.PlonePAS.interfaces.plugins import IUserManagement
from Products.PluggableAuthService.PluggableAuthService import \
_SWALLOWABLE_PLUGIN_EXCEPTIONS
from Products.PluggableAuthService.PluggableAuthService import \
PluggableAuthService
from Products.PlonePAS.pas import _doDelUser
from Products.PluggableAuthService.interfaces.events import IPASEvent
from zope.interface import implements
from Products.PluggableAuthService.events import PASEvent
class IPrincipalBeforeDeletedEvent(IPASEvent):
"""A user is marked to be removed but still into database.
"""
class PrincipalBeforeDeleted(PASEvent):
implements(IPrincipalBeforeDeletedEvent)
def _doDelUser(self, id):
"""
Given a user id, hand off to a deleter plugin if available.
Fix: Add PrincipalBeforeDeleted notification
"""
plugins = self._getOb('plugins')
userdeleters = plugins.listPlugins(IUserManagement)
if not userdeleters:
raise NotImplementedError(
"There is no plugin that can delete users.")
for userdeleter_id, userdeleter in userdeleters:
# vvv Custom
notify(PrincipalBeforeDeleted(id))
# ^^^ Custom
try:
userdeleter.doDeleteUser(id)
except _SWALLOWABLE_PLUGIN_EXCEPTIONS:
pass
else:
notify(PrincipalDeleted(id))
PluggableAuthService._doDelUser = _doDelUser
Then added a subscriber for this event:
In configure.zcml:
<subscriber
for="* mycontent.patches.IPrincipalBeforeDeletedEvent"
handler="mycontent.globalhandlers.userBeforeDeleted" />
In globalhandlers.py:
from Products.CMFCore.utils import getToolByName
def handleEventFail(func):
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception:
logger.exception('in {0}'.format(func.__name__))
return wrapper
#handleEventFail
def userBeforeDeleted(user_id, event):
""" Notify deleted user about this action. """
membership_tool = getToolByName(api.portal.get(), 'portal_membership')
user = membership_tool.getMemberById(user_id)
email = user.getProperty('email')
mail_text = """
Hi!
Your account ({0}) was deleted.
Best regards,
Our Best Team""".format(user_id)
print mail_text
print email
# TODO send mail

Related

FastAPI - dependency inside Middleware?

I am building a browser game where every user has 4 types of ressources and each users produce more ressources based on the level of their farms.
What I am trying to do, is whenever a given user is logged in, I want to recalculate it's current ressources whenever he is refreshing the page or performing any action.
Seems the middleware is the right tool for my need but I am a bit confused on the implementation with my current architecture (multiple routers). What would be the cleanest way to call a function to perform ressources recalculation before performing any other API calls?
This is what I have tried so far (example middleware):
app.py (without middleware):
from fastapi import FastAPI, Depends, Request
from src.api.v1.village import village_router
from src.api.v1.auth import auth_router
from src.api.v1.admin import admin_router
from src.core.auth import get_current_user
from src.core.config import *
def create_app() -> FastAPI:
root_app = FastAPI()
root_app.include_router(
auth_router,
prefix="/api/v1",
tags=["auth"],
)
root_app.include_router(
admin_router,
prefix="/api/v1",
tags=["admin"],
dependencies=[Depends(get_current_user)],
)
root_app.include_router(
village_router,
prefix="/api/v1",
tags=["village"],
)
return root_app
I then added an helloworld middleware and add the get_current_user as a dependency because a user must be logged_in to perform the calculations.
app.py (with middleware):
from fastapi import FastAPI, Depends, Request
from src.api.v1.village import village_router
from src.api.v1.auth import auth_router
from src.api.v1.admin import admin_router
from src.core.auth import get_current_user
from src.core.config import *
import time
def create_app() -> FastAPI:
root_app = FastAPI()
root_app.include_router(
auth_router,
prefix="/api/v1",
tags=["auth"],
)
root_app.include_router(
admin_router,
prefix="/api/v1",
tags=["admin"],
dependencies=[Depends(get_current_user)],
)
root_app.include_router(
village_router,
prefix="/api/v1",
tags=["village"],
)
#root_app.middleware("http")
async def add_process_time_header(
request: Request, call_next, current_user=Depends(get_current_user)
):
start_time = time.time()
response = await call_next(request)
process_time = time.time() - start_time
response.headers["X-Process-Time"] = str(process_time)
print("middleware call")
return response
return root_app
Seems the dependency is ignored because the middleware is called even when I am not logged in, which is not the case for my protected_routes (I am getting a 401 error on my routes if I a not logged in).
async def get_current_user(
session=Depends(get_db), token: str = Depends(oauth2_scheme)
) -> UserAuth:
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[AUTH_TOKEN_ALGO])
email: str = payload.get("email")
user_id: str = payload.get("user_id")
if email is None:
raise ValueError("A very specific bad thing happened.")
token_data = UserJWTToken(user_id=user_id, email=email)
except jwt.PyJWTError:
raise ValueError("A very specific bad thing happened.")
user = get_user_by_email(session, token_data.email)
if user is None:
raise ValueError("A very specific bad thing happened.")
return user
You can make use of the Global Dependencies. Here is one example that may help you in this situation
from fastapi import Depends, FastAPI, Request
def get_db_session():
print("Calling 'get_db_session(...)'")
return "Some Value"
def get_current_user(session=Depends(get_db_session)):
print("Calling 'get_current_user(...)'")
return session
def recalculate_resources(request: Request, current_user=Depends(get_current_user)):
print("calling 'recalculate_resources(...)'")
request.state.foo = current_user
app = FastAPI(dependencies=[Depends(recalculate_resources)])
#app.get("/")
async def root(request: Request):
return {"foo_from_dependency": request.state.foo}

Plone: custom variable in content rule

I have defined a dexterity content type myaddon.subscriber (subscriber = participant at an event).
I added a Content rule to send an email when a subscriber is approved (workflow state change to this one).
Each subscriber has an email address (email - field).
In Edit Mail Action form I see I can use variables like ${user_email} in Email recipients (Required)
The email where you want to send this message. To send it to different email addresses, just separate them with ,
This is working. In my case user_email is the email of logged in user - the person who approve the participant. The messages are sent when the state is changed. Perfect.
I need to define a variable ${subscriber_email} that will have the myaddon.subscriber.email value. How can I do this? I'm trying to find an example. So, how to use an field (email) of current changed object (subscriber) as variable in this Mail Action?
You can use IContextWrapper from plone.stringinterp 1.0.14+:
>>> new_context = IContextWrapper(context)(
... email=u"subscriber#example.com"
... )
>>> class SubscriberEmailSubstitution(BaseSubstitution):
... def safe_call(self):
... return self.wrapper.email
>>> sm = getGlobalSiteManager()
>>> sm.registerAdapter(SubscriberEmailSubstitution, (Interface, ), IStringSubstitution, name=u"subscriber_email")
>>> getAdapter(new_context, IStringSubstitution, 'subscriber_email')()
u'subscriber#example.com'
Imports:
>>> from zope.interface import Interface
>>> from plone.stringinterp.interfaces import IStringSubstitution
>>> from plone.stringinterp.interfaces import IStringSubstitutionInfo
>>> from zope.component import getGlobalSiteManager, getAdapter
>>> from plone.stringinterp.adapters import BaseSubstitution
>>> from plone.stringinterp.interfaces import IContextWrapper
See more stringinterp doctests.
Also see a fully working example within eea.pdf add-on.

Programmatically created user is active?

Django 1.9.6
I just want to test if anonymous user is redirected to login page.
I've created some user. It is definitely not registered.
Could you help me understand, why its property is_active is set to True.
Well, I stopped at pdb breakpoint and:
-> pdb.set_trace()
(Pdb) request.user.is_active
True
I have to explicitly set user.is_active = False. But why is the user active by default is a mystery to me. Nobody has activated it, I would say.
The code is below.
from django.test import TestCase
from .views import HomePageView
from django.http.request import HttpRequest
from django.contrib.auth.models import User
class GeneralTest(TestCase):
def test_anonymous_user_redirected_to_login_page(self):
user = User(username='anonymous', email='vvv#mail.ru', password='ttrrttrr')
request = HttpRequest()
request.user = user
hpv = HomePageView()
response = hpv.get(request)
pdb.set_trace()

plone.registry - How do I capture a change in registry when IRecordModifiedEvent is fired?

I'm trying to replicate an example of detecting the change in registry when IRecordModified is supposed to be fired.
https://pypi.python.org/pypi/plone.app.registry#registry-events
I try to adapt this to what I am doing, but a print statement I placed isn't firing.
In my registry.xml
<registry>
<records interface="my.product.utils.db_settings.IDBSettings">
</registry>
In my events.py
from my.product.utils.db_settings import IDBSettings
#adapter(IDBSettings, IRecordModifiedEvent)
def detectDBSettingsChange(settings, event):
print "detectDBSettingsChange"
In the module db_settings.py, which contain the control panel , I have:
from plone.app.registry.browser import controlpanel
class IDBSettings(Interface):
db_string = schema.TextLine(title=u"Database String",
description=u"String for database connection",
default=u"Some value"
)
class DBSettingsEditForm(controlpanel.RegistryEditForm):
schema = IDBSettings
label = u"Database settings"
description = u"String setup"
def updateFields(self):
super(DBSettingsEditForm, self).updateFields()
def updateWidgets(self):
super(DBSettingsEditForm, self).updateWidgets()
class DBSettingsControlPanel(controlpanel.ControlPanelFormWrapper):
form = DBSettingsEditForm
In my configure.zcml in utils:
<include package="plone.app.registry" />
<browser:page
name="database-settings"
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
class=".db_settings.DBSettingsControlPanel"
permission="cmf.ManagePortal"
/>
When I go into the control panel, change the value, and then save, detectDBSettingsChange doesn't seem to work as the print statement is ignored. Am I working with the wrong event to capture the change in my registry?
I think you need a subscriber like described in the docs. I'm not sure that the adapter in your events.py is sufficient. Perhaps you can use provideHandler Method like described in plone.registry
You need to add a subscriber and register it like this in your configure.zcml file:
<configure xmlns="http://namespaces.zope.org/zope">
<subscriber
for="plone.registry.interfaces.IRecordModifiedEvent"
handler="your.package.your_subscriber"
/>
</configure>
Check the collective.fingerpointing package for a working example of this.

How to restrict image file extension on Plone?

I have a Plone application in which I can upload images, which are ATImages. I want to validate the extension file (mainly to forbid pdf files). There are created with a url call like http://blablba.com/createObject?type_name=Image
I have tried setting the /content_type_registry with file extensions associated with images, with no success (pdf upload still work)
I guess I could write a new class extending ATImages, create a form with a validator, but it looks a little bit complicated and it seemed that some settings on content_type registry would be enough (or elsewhere).
How would you do that ? (forbid pdf ?)
thx
We had a similar problem.
Archetypes fires several events during its magic, amongst others a "post validation event" (IObjectPostValidation). This way we added a check for the content-type.
subscriber (zcml):
<subscriber provides="Products.Archetypes.interfaces.IObjectPostValidation"
factory=".subscribers.ImageFieldContentValidator" />
quick and dirty implementation:
from Products.Archetypes.interfaces.field import IImageField
from plone.app.blob.interfaces import IBlobImageField
from Products.Archetypes.interfaces import IObjectPostValidation
from zope.interface import implements
from zope.component import adapts
# import your message factory as _
ALLOWED_IMAGETYPES = ['image/png',
'image/jpeg',
'image/gif',
'image/pjpeg',
'image/x-png']
class ImageFieldContentValidator(object):
"""Validate that the ImageField really contains a Imagefile.
Show a Errormessage if it doesn't.
"""
implements(IObjectPostValidation)
adapts(IBaseObject)
img_interfaces = [IBlobImageField, IImageField]
msg = _(u"error_not_image",
default="The File you wanted to upload is no image")
def __init__(self, context):
self.context = context
def __call__(self, request):
for fieldname in self.context.Schema().keys():
field = self.context.getField(fieldname)
if True in [img_interface.providedBy(field) \
for img_interface in self.img_interfaces]:
item = request.get(fieldname + '_file', None)
if item:
header = item.headers
ct = header.get('content-type')
if ct in ALLOWED_IMAGETYPES:
return
else:
return {fieldname: self.msg}

Resources