diff --git a/circle/dashboard/views/util.py b/circle/dashboard/views/util.py
index ad3235f..c26909b 100644
--- a/circle/dashboard/views/util.py
+++ b/circle/dashboard/views/util.py
@@ -45,7 +45,7 @@ from django.views.decorators.debug import sensitive_post_parameters
 from django.views.generic import DetailView, View, DeleteView, FormView
 from django.views.generic.detail import SingleObjectMixin
 from keystoneauth1.identity import v3
-from vm.models.instance import TemplateGroupMember, TemplateUserMember
+from vm.models.instance import TemplateGroupMember, TemplateUserMember, InstanceTemplate
 from openstack_auth.utils import fix_auth_url_version
 from vm.models import Instance
 
@@ -419,6 +419,7 @@ class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
         from openstack_api import keystone
         return keystone.group_list(request=self.request, user=self.request.user)
 
+    # TODO: extract this to openstack_api
     def __get_glance_admin_client(self, project_id):
         from keystoneauth1 import session
         from glanceclient import Client
@@ -433,6 +434,7 @@ class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
 
         return Client('2', session=session)
 
+    # TODO: extract this to openstack_api
     def __get_keystone_admin_client(self):
         from keystoneauth1 import session
         from keystoneclient.v3 import client
@@ -601,6 +603,86 @@ class AclUpdateView(LoginRequiredMixin, View, SingleObjectMixin):
         return redirect("%s#access" % self.template.get_absolute_url())
 
 
+class UpdateSharedTemplates(View):
+    # TODO: extract these to openstack_api
+    def __get_glance_admin_client(self):
+        from keystoneauth1 import session
+        from glanceclient import Client
+
+        auth = v3.Password(
+            auth_url=fix_auth_url_version(settings.OPENSTACK_KEYSTONE_URL),
+            user_id=settings.OPENSTACK_CIRCLE_USERID,
+            password=settings.OPENSTACK_CIRCLE_PASSWORD,
+            project_id=self.project_id,
+        )
+        session = session.Session(auth=auth, verify=False)
+
+        return Client('2', session=session)
+
+    # TODO: extract this to openstack_api
+    def __get_keystone_admin_client(self):
+        from keystoneauth1 import session
+        from keystoneclient.v3 import client
+
+        auth = v3.Password(
+            auth_url=fix_auth_url_version(settings.OPENSTACK_KEYSTONE_URL),
+            user_id=settings.OPENSTACK_CIRCLE_USERID,
+            password=settings.OPENSTACK_CIRCLE_PASSWORD,
+        )
+        sess = session.Session(auth=auth, verify=False)
+        return client.Client(session=sess, interface=settings.OPENSTACK_INTERFACE)
+
+    def __get_templates_of_snapshots(self):
+        images = openstack_api.glance.image_list_detailed(self.request)[0]  # TODO: why nested lists?
+        snapshot_ids = [
+            i.id for i in images if hasattr(i, 'image_location') and i.image_location == 'snapshot'
+        ]
+        return InstanceTemplate.objects.filter(image_id__in=snapshot_ids)
+
+    def __list_users_of_group(self, group_id):
+        keystone = self.__get_keystone_admin_client()
+        return keystone.users.list(group=group_id)
+
+    def __is_member_of_groups(self, template):
+        for group in template.groups.all():
+            group_members = self.__list_users_of_group(group.group_id)
+            for group_member in group_members:
+                if group_member.default_project_id == self.project_id:
+                    return True
+        return False
+
+    def __cleanup_snapshot(self, template):
+        try:
+            template.users.get(project_id=self.project_id)
+            return  # member is assigned as user
+        except TemplateUserMember.DoesNotExist:
+            pass
+
+        if not self.__is_member_of_groups(template):
+            self.glance.image_members.delete(template.image_id, self.project_id)
+
+    def __cleanup_snapshots(self):
+        templates_of_existing_snaps = self.__get_templates_of_snapshots()
+        for t in templates_of_existing_snaps:
+            self.__cleanup_snapshot(t)
+
+    def __update_shared_templates(self):
+        self.__cleanup_snapshots()
+
+    def post(self, request):
+        if not hasattr(request.POST, 'secret') or \
+           not hasattr(request.POST, 'project_id'):
+            return HttpResponse(status=400)
+
+        secret = request.POST['secret']
+        if secret != settings.SESSIONHOOK_SHARED_SECRET:
+            return HttpResponse(status=401)
+
+        self.project_id = request.POST['project_id']
+        self.glance = self.__get_glance_admin_client()
+        self.__update_shared_templates()
+
+
 class GraphMixin(object):
     graph_time_options = [
         {'time': "1h", 'name': _("1 hour")},