commit 564a2292b98e2ef98ee021d85eb8bd8d82fc573f
Author: Kostas Papadimitriou <kpap@grnet.gr>
Date:   Fri Jan 11 12:47:19 2013 +0200

    Auth providers improvements
    
    - Enrich login/logout messages.
    - Clear unverified accounts when user adds the same third party account
      to an existing account.
    - Other minor improvements.

diff --git a/snf-astakos-app/astakos/im/auth_providers.py b/snf-astakos-app/astakos/im/auth_providers.py
index e95f3e4..f7488d4 100644
--- a/snf-astakos-app/astakos/im/auth_providers.py
+++ b/snf-astakos-app/astakos/im/auth_providers.py
@@ -79,6 +79,10 @@ class AuthProvider(object):
     one_per_user = False
     login_prompt = _('Login using ')
     primary_login_prompt = _('Login using ')
+    login_message = None
+    logout_message = 'You may still be logged in at "%(provider)s". Consider logging out.'
+    remote_authenticate = True
+    remote_logout_url = None
 
     def get_message(self, msg, **kwargs):
         params = kwargs
@@ -105,6 +109,13 @@ class AuthProvider(object):
             if override != None:
                 setattr(self, key, override)
 
+        self.login_message = self.login_message or self.get_title_display
+        if self.logout_message and "%" in self.logout_message:
+            self.logout_message = self.logout_message % {'provider':
+                                                         self.get_login_message_display}
+        else:
+            self.logout_message = self.logout_message or ''
+
     def __getattr__(self, key):
         if not key.startswith('get_'):
             return super(AuthProvider, self).__getattribute__(key)
@@ -118,6 +129,12 @@ class AuthProvider(object):
         else:
             return super(AuthProvider, self).__getattr__(key)
 
+    def get_logout_message(self):
+        content = ''
+        if self.remote_logout_url:
+            content = '<a href="%s" title="Logout from %%s"></a>' % self.remote_logou_url
+        return content % (self.get_logout_message_display % self.get_title_display)
+
     def get_setting(self, name, default=None):
         attr = 'ASTAKOS_AUTH_PROVIDER_%s_%s' % (self.module.upper(), name.upper())
         attr_sec = 'ASTAKOS_%s_%s' % (self.module.upper(), name.upper())
@@ -158,6 +175,8 @@ class LocalAuthProvider(AuthProvider):
     signup_prompt = _('New to ~okeanos ?')
     signup_link_prompt = _('create an account now')
     login_view = 'password_change'
+    remote_authenticate = False
+    logout_message = ''
 
     one_per_user = True
 
diff --git a/snf-astakos-app/astakos/im/forms.py b/snf-astakos-app/astakos/im/forms.py
index fc2e83c..c1d6616 100644
--- a/snf-astakos-app/astakos/im/forms.py
+++ b/snf-astakos-app/astakos/im/forms.py
@@ -274,7 +274,12 @@ class ThirdPartyUserCreationForm(forms.ModelForm, StoreUserMixin):
         if not email:
             raise forms.ValidationError(_(astakos_messages.REQUIRED_FIELD))
         if reserved_verified_email(email):
-            raise forms.ValidationError(_(astakos_messages.EMAIL_USED))
+            provider = auth_providers.get_provider(self.request.REQUEST.get('provider', 'local'))
+            extra_message = _(astakos_messages.EXISTING_EMAIL_THIRD_PARTY_NOTIFICATION) % \
+                    (provider.get_title_display, reverse('edit_profile'))
+
+            raise forms.ValidationError(_(astakos_messages.EMAIL_USED) + ' ' + \
+                                        extra_message)
         return email
 
     def clean_has_signed_terms(self):
diff --git a/snf-astakos-app/astakos/im/messages.py b/snf-astakos-app/astakos/im/messages.py
index 9ae09c1..36ff2b1 100644
--- a/snf-astakos-app/astakos/im/messages.py
+++ b/snf-astakos-app/astakos/im/messages.py
@@ -67,8 +67,8 @@ USER_LEFT_GROUP                         =   'User %(realname)s left the project.
 USER_MEMBERSHIP_REJECTED                =   'User\'s %(realname)s request to join the project has been rejected.'
 MEMBER_REMOVED                          =   'User %(realname)s has been successfully removed from the project.'
 BILLING_ERROR                           =   'Service response status: %(status)d'
-LOGOUT_SUCCESS                          =   'You are now logged out.'
-LOGIN_SUCCESS                           =   'You are now logged in.'
+LOGOUT_SUCCESS                          =   'You are now logged out from your ~okeanos account.'
+LOGIN_SUCCESS                           =   'You have logged in to your ~okeanos account using "%s".'
 
 GENERIC_ERROR                           =   'Hmm... It seems something bad has happened, and we don\'t know the details right now. \
                                                Please contact the administrators by email for more details.'
@@ -197,6 +197,9 @@ AUTH_PROVIDER_INVALID_LOGIN                  =   "No account exists."
 AUTH_PROVIDER_REQUIRED                       =   "%(provider)s login method is required. Add one from your profile page."
 AUTH_PROVIDER_CANNOT_CHANGE_PASSWORD         =   "Changing password is not supported."
 
+EXISTING_EMAIL_THIRD_PARTY_NOTIFICATION      =   "You can add '%s' login method to your existing account from your " \
+                                                 " <a href='%s'>profile page</a>"
+
 messages = locals().keys()
 for msg in messages:
     if msg == msg.upper():
diff --git a/snf-astakos-app/astakos/im/migrations/0056_auto__chg_field_pendingthirdpartyuser_first_name__chg_field_pendingthi.py b/snf-astakos-app/astakos/im/migrations/0056_auto__chg_field_pendingthirdpartyuser_first_name__chg_field_pendingthi.py
new file mode 100644
index 0000000..da963bd
--- /dev/null
+++ b/snf-astakos-app/astakos/im/migrations/0056_auto__chg_field_pendingthirdpartyuser_first_name__chg_field_pendingthi.py
@@ -0,0 +1,272 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Changing field 'PendingThirdPartyUser.first_name'
+        db.alter_column('im_pendingthirdpartyuser', 'first_name', self.gf('django.db.models.fields.CharField')(max_length=30, null=True))
+
+        # Changing field 'PendingThirdPartyUser.last_name'
+        db.alter_column('im_pendingthirdpartyuser', 'last_name', self.gf('django.db.models.fields.CharField')(max_length=30, null=True))
+
+        # Changing field 'PendingThirdPartyUser.affiliation'
+        db.alter_column('im_pendingthirdpartyuser', 'affiliation', self.gf('django.db.models.fields.CharField')(max_length=255, null=True))
+
+
+    def backwards(self, orm):
+        
+        # Changing field 'PendingThirdPartyUser.first_name'
+        db.alter_column('im_pendingthirdpartyuser', 'first_name', self.gf('django.db.models.fields.CharField')(default='', max_length=30))
+
+        # Changing field 'PendingThirdPartyUser.last_name'
+        db.alter_column('im_pendingthirdpartyuser', 'last_name', self.gf('django.db.models.fields.CharField')(default='', max_length=30))
+
+        # Changing field 'PendingThirdPartyUser.affiliation'
+        db.alter_column('im_pendingthirdpartyuser', 'affiliation', self.gf('django.db.models.fields.CharField')(default='', max_length=255))
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'im.additionalmail': {
+            'Meta': {'object_name': 'AdditionalMail'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.approvalterms': {
+            'Meta': {'object_name': 'ApprovalTerms'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 1, 11, 10, 45, 47, 968841)', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'location': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.astakosuser': {
+            'Meta': {'object_name': 'AstakosUser', '_ormbases': ['auth.User']},
+            'activation_sent': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_signed_terms': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'disturbed_quota': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'email_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_credits': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'has_signed_terms': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
+            'policy': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.AstakosUserQuota']", 'symmetrical': 'False'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'updated': ('django.db.models.fields.DateTimeField', [], {}),
+            'user_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True', 'null': 'True'})
+        },
+        'im.astakosuserauthprovider': {
+            'Meta': {'ordering': "('module', 'created')", 'unique_together': "(('identifier', 'module', 'user'),)", 'object_name': 'AstakosUserAuthProvider'},
+            'active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'affiliation': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'auth_backend': ('django.db.models.fields.CharField', [], {'default': "'astakos'", 'max_length': '255'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'info_data': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'module': ('django.db.models.fields.CharField', [], {'default': "'local'", 'max_length': '255'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'auth_providers'", 'to': "orm['im.AstakosUser']"})
+        },
+        'im.astakosuserquota': {
+            'Meta': {'unique_together': "(('resource', 'user'),)", 'object_name': 'AstakosUserQuota'},
+            'capacity': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'export_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'import_limit': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'quantity': ('django.db.models.fields.BigIntegerField', [], {'null': 'True'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"})
+        },
+        'im.chain': {
+            'Meta': {'object_name': 'Chain'},
+            'chain': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.emailchange': {
+            'Meta': {'object_name': 'EmailChange'},
+            'activation_key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'new_email_address': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'requested_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2013, 1, 11, 10, 45, 47, 969658)'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'emailchanges'", 'unique': 'True', 'to': "orm['im.AstakosUser']"})
+        },
+        'im.invitation': {
+            'Meta': {'object_name': 'Invitation'},
+            'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
+            'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.AstakosUser']"}),
+            'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'})
+        },
+        'im.pendingthirdpartyuser': {
+            'Meta': {'unique_together': "(('provider', 'third_party_identifier'),)", 'object_name': 'PendingThirdPartyUser'},
+            'affiliation': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'info': ('django.db.models.fields.TextField', [], {'default': "''", 'null': 'True', 'blank': 'True'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'third_party_identifier': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'im.project': {
+            'Meta': {'object_name': 'Project'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'project'", 'unique': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'creation_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'deactivation_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'deactivation_reason': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'is_modified': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'last_approval_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.AstakosUser']", 'through': "orm['im.ProjectMembership']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80', 'db_index': 'True'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'im.projectapplication': {
+            'Meta': {'unique_together': "(('chain', 'id'),)", 'object_name': 'ProjectApplication'},
+            'applicant': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_applied'", 'to': "orm['im.AstakosUser']"}),
+            'chain': ('django.db.models.fields.IntegerField', [], {}),
+            'comments': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'end_date': ('django.db.models.fields.DateTimeField', [], {}),
+            'homepage': ('django.db.models.fields.URLField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'issue_date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'limit_on_members_number': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True'}),
+            'member_join_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'member_leave_policy': ('django.db.models.fields.IntegerField', [], {}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80'}),
+            'owner': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects_owned'", 'to': "orm['im.AstakosUser']"}),
+            'precursor_application': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['im.ProjectApplication']", 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'resource_grants': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['im.Resource']", 'null': 'True', 'through': "orm['im.ProjectResourceGrant']", 'blank': 'True'}),
+            'start_date': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'state': ('django.db.models.fields.CharField', [], {'default': "'Pending'", 'max_length': '80'})
+        },
+        'im.projectmembership': {
+            'Meta': {'unique_together': "(('person', 'project'),)", 'object_name': 'ProjectMembership'},
+            'acceptance_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'db_index': 'True'}),
+            'application': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'memberships'", 'null': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'is_pending': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'db_index': 'True'}),
+            'leave_request_date': ('django.db.models.fields.DateField', [], {'null': 'True'}),
+            'pending_application': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pending_memebrships'", 'null': 'True', 'to': "orm['im.ProjectApplication']"}),
+            'pending_serial': ('django.db.models.fields.BigIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'person': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.AstakosUser']"}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Project']"}),
+            'request_date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime(2013, 1, 11, 10, 45, 47, 973568)'}),
+            'state': ('django.db.models.fields.IntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'im.projectmembershiphistory': {
+            'Meta': {'object_name': 'ProjectMembershipHistory'},
+            'date': ('django.db.models.fields.DateField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'person': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'project': ('django.db.models.fields.BigIntegerField', [], {}),
+            'reason': ('django.db.models.fields.IntegerField', [], {}),
+            'serial': ('django.db.models.fields.BigIntegerField', [], {})
+        },
+        'im.projectresourcegrant': {
+            'Meta': {'unique_together': "(('resource', 'project_application'),)", 'object_name': 'ProjectResourceGrant'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'member_capacity': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
+            'member_export_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
+            'member_import_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
+            'project_application': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.ProjectApplication']", 'null': 'True'}),
+            'project_capacity': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
+            'project_export_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
+            'project_import_limit': ('synnefo.lib.db.intdecimalfield.IntDecimalField', [], {'default': '100000000000000000000000000000000L', 'max_digits': '38', 'decimal_places': '0'}),
+            'resource': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Resource']"})
+        },
+        'im.resource': {
+            'Meta': {'unique_together': "(('name', 'service'),)", 'object_name': 'Resource'},
+            'desc': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'group': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'meta': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['im.ResourceMetadata']", 'symmetrical': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'service': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['im.Service']"}),
+            'unit': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'})
+        },
+        'im.resourcemetadata': {
+            'Meta': {'object_name': 'ResourceMetadata'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'im.serial': {
+            'Meta': {'object_name': 'Serial'},
+            'serial': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'})
+        },
+        'im.service': {
+            'Meta': {'ordering': "('order',)", 'object_name': 'Service'},
+            'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
+            'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'icon': ('django.db.models.fields.FilePathField', [], {'max_length': '100', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}),
+            'order': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
+            'url': ('django.db.models.fields.FilePathField', [], {'max_length': '100'})
+        },
+        'im.sessioncatalog': {
+            'Meta': {'object_name': 'SessionCatalog'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'session_key': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sessions'", 'null': 'True', 'to': "orm['im.AstakosUser']"})
+        }
+    }
+
+    complete_apps = ['im']
diff --git a/snf-astakos-app/astakos/im/models.py b/snf-astakos-app/astakos/im/models.py
index 6b87adf..ed1f5b2 100644
--- a/snf-astakos-app/astakos/im/models.py
+++ b/snf-astakos-app/astakos/im/models.py
@@ -533,6 +533,7 @@ class AstakosUser(User):
         if 'identifier' in kwargs:
             try:
                 # provider with specified params already exist
+                kwargs['user__email_verified'] = True
                 existing_user = AstakosUser.objects.get_auth_provider_user(provider,
                                                                    **kwargs)
             except AstakosUser.DoesNotExist:
@@ -577,6 +578,8 @@ class AstakosUser(User):
                 info_data = json.dumps(info_data)
 
         if self.can_add_auth_provider(provider, **kwargs):
+            AstakosUserAuthProvider.objects.remove_unverified_providers(provider,
+                                                                **kwargs)
             self.auth_providers.create(module=provider, active=True,
                                        info_data=info_data,
                                        **kwargs)
@@ -692,6 +695,15 @@ class AstakosUserAuthProviderManager(models.Manager):
     def active(self, **filters):
         return self.filter(active=True, **filters)
 
+    def remove_unverified_providers(self, provider, **filters):
+        try:
+            existing = self.filter(module=provider, user__email_verified=False, **filters)
+            for p in existing:
+                p.user.delete()
+        except:
+            pass
+
+
 
 class AstakosUserAuthProvider(models.Model):
     """
@@ -960,9 +972,12 @@ class PendingThirdPartyUser(models.Model):
     third_party_identifier = models.CharField(_('Third-party identifier'), max_length=255, null=True, blank=True)
     provider = models.CharField(_('Provider'), max_length=255, blank=True)
     email = models.EmailField(_('e-mail address'), blank=True, null=True)
-    first_name = models.CharField(_('first name'), max_length=30, blank=True)
-    last_name = models.CharField(_('last name'), max_length=30, blank=True)
-    affiliation = models.CharField('Affiliation', max_length=255, blank=True)
+    first_name = models.CharField(_('first name'), max_length=30, blank=True,
+                                  null=True)
+    last_name = models.CharField(_('last name'), max_length=30, blank=True,
+                                 null=True)
+    affiliation = models.CharField('Affiliation', max_length=255, blank=True,
+                                   null=True)
     username = models.CharField(_('username'), max_length=30, unique=True, help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
     token = models.CharField(_('Token'), max_length=255, null=True, blank=True)
     created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
diff --git a/snf-astakos-app/astakos/im/target/google.py b/snf-astakos-app/astakos/im/target/google.py
index bf3ffaf..5804c33 100644
--- a/snf-astakos-app/astakos/im/target/google.py
+++ b/snf-astakos-app/astakos/im/target/google.py
@@ -182,7 +182,10 @@ def authenticated(
                                     user,
                                     request.GET.get('next'),
                                     'renew' in request.GET)
-            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
+
+            provider = auth_providers.get_provider('google')
+            messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
+                             _(provider.get_login_message_display))
             add_pending_auth_provider(request, third_party_key)
             response.set_cookie('astakos_last_login_method', 'google')
             return response
diff --git a/snf-astakos-app/astakos/im/target/linkedin.py b/snf-astakos-app/astakos/im/target/linkedin.py
index c489d78..ca5e2e7 100644
--- a/snf-astakos-app/astakos/im/target/linkedin.py
+++ b/snf-astakos-app/astakos/im/target/linkedin.py
@@ -175,7 +175,9 @@ def authenticated(
                                     user,
                                     request.GET.get('next'),
                                     'renew' in request.GET)
-            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
+            provider = auth_providers.get_provider('linkedin')
+            messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
+                             _(provider.get_login_message_display))
             add_pending_auth_provider(request, third_party_key)
             response.set_cookie('astakos_last_login_method', 'linkedin')
             return response
diff --git a/snf-astakos-app/astakos/im/target/local.py b/snf-astakos-app/astakos/im/target/local.py
index 951e15e..9ceccb6 100644
--- a/snf-astakos-app/astakos/im/target/local.py
+++ b/snf-astakos-app/astakos/im/target/local.py
@@ -51,6 +51,7 @@ from astakos.im.settings import (RATELIMIT_RETRIES_ALLOWED,
 import astakos.im.messages as astakos_messages
 from astakos.im.views import requires_auth_provider
 from astakos.im import settings
+from astakos.im import auth_providers
 
 from ratelimit.decorators import ratelimit
 
@@ -118,7 +119,9 @@ def login(request, on_failure='im/login.html'):
         except PendingThirdPartyUser.DoesNotExist:
             messages.error(request, _(astakos_messages.AUTH_PROVIDER_ADD_FAILED))
 
-    messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
+    provider = auth_providers.get_provider('local')
+    messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
+                     _(provider.get_login_message_display))
     response.set_cookie('astakos_last_login_method', 'local')
     return response
 
diff --git a/snf-astakos-app/astakos/im/target/shibboleth.py b/snf-astakos-app/astakos/im/target/shibboleth.py
index 5172044..1fdea7e 100644
--- a/snf-astakos-app/astakos/im/target/shibboleth.py
+++ b/snf-astakos-app/astakos/im/target/shibboleth.py
@@ -87,33 +87,33 @@ def login(
     tokens = request.META
     third_party_key = get_pending_key(request)
 
-#    try:
-#        eppn = tokens.get(Tokens.SHIB_EPPN)
-#        if not eppn:
-#            raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_EPPN) % {
-#                'domain': settings.BASEURL,
-#                'contact_email': settings.DEFAULT_CONTACT_EMAIL
-#            })
-#        if Tokens.SHIB_DISPLAYNAME in tokens:
-#            realname = tokens[Tokens.SHIB_DISPLAYNAME]
-#        elif Tokens.SHIB_CN in tokens:
-#            realname = tokens[Tokens.SHIB_CN]
-#        elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
-#            realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
-#        else:
-#            if settings.SHIBBOLETH_REQUIRE_NAME_INFO:
-#                raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
-#            else:
-#                realname = ''
-#
-#    except KeyError, e:
-#        # invalid shibboleth headers, redirect to login, display message
-#        messages.error(request, e.message)
-#        return HttpResponseRedirect(login_url(request))
-
-#    affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
-#    email = tokens.get(Tokens.SHIB_MAIL, '')
-    eppn, email, realname, affiliation = 'spapagian@grnet-hq.admin.grnet.gr', 'spapagian@.grnet.gr', 'sff', None
+    try:
+        eppn = tokens.get(Tokens.SHIB_EPPN)
+        if not eppn:
+            raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_EPPN) % {
+                'domain': settings.BASEURL,
+                'contact_email': settings.DEFAULT_CONTACT_EMAIL
+            })
+        if Tokens.SHIB_DISPLAYNAME in tokens:
+            realname = tokens[Tokens.SHIB_DISPLAYNAME]
+        elif Tokens.SHIB_CN in tokens:
+            realname = tokens[Tokens.SHIB_CN]
+        elif Tokens.SHIB_NAME in tokens and Tokens.SHIB_SURNAME in tokens:
+            realname = tokens[Tokens.SHIB_NAME] + ' ' + tokens[Tokens.SHIB_SURNAME]
+        else:
+            if settings.SHIBBOLETH_REQUIRE_NAME_INFO:
+                raise KeyError(_(astakos_messages.SHIBBOLETH_MISSING_NAME))
+            else:
+                realname = ''
+
+    except KeyError, e:
+        # invalid shibboleth headers, redirect to login, display message
+        messages.error(request, e.message)
+        return HttpResponseRedirect(login_url(request))
+
+    affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, 'Shibboleth')
+    email = tokens.get(Tokens.SHIB_MAIL, '')
+    #eppn, email, realname, affiliation = 'test@grnet-hq.admin.grnet.gr', 'test@grnet.gr', 'sff', None
     provider_info = {'eppn': eppn, 'email': email, 'name': realname}
     userid = eppn
 
@@ -151,7 +151,9 @@ def login(
                                     user,
                                     request.GET.get('next'),
                                     'renew' in request.GET)
-            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
+            provider = auth_providers.get_provider('shibboleth')
+            messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
+                             _(provider.get_login_message_display))
             add_pending_auth_provider(request, third_party_key)
             response.set_cookie('astakos_last_login_method', 'local')
             return response
diff --git a/snf-astakos-app/astakos/im/target/twitter.py b/snf-astakos-app/astakos/im/target/twitter.py
index 3ca78a6..763e208 100644
--- a/snf-astakos-app/astakos/im/target/twitter.py
+++ b/snf-astakos-app/astakos/im/target/twitter.py
@@ -168,7 +168,9 @@ def authenticated(
                                     user,
                                     request.GET.get('next'),
                                     'renew' in request.GET)
-            messages.success(request, _(astakos_messages.LOGIN_SUCCESS))
+            provider = auth_providers.get_provider('twitter')
+            messages.success(request, _(astakos_messages.LOGIN_SUCCESS) %
+                             _(provider.get_login_message_display))
             add_pending_auth_provider(request, third_party_key)
             response.set_cookie('astakos_last_login_method', 'twitter')
             return response
diff --git a/snf-astakos-app/astakos/im/templates/im/signup.html b/snf-astakos-app/astakos/im/templates/im/signup.html
index bb01b2d..59a7bde 100644
--- a/snf-astakos-app/astakos/im/templates/im/signup.html
+++ b/snf-astakos-app/astakos/im/templates/im/signup.html
@@ -14,7 +14,7 @@
       <div class="form-stacked">
         <form action="{% url signup %}" method="post"
             class="innerlabels signup">{% csrf_token %}
-          <h2><span>SIGN UP</span></h2>
+            <h2><span>SIGN UP</span></h2>
             <input type="hidden" name="next" value="{{ next }}">
             <input type="hidden" name="code" value="{{ code }}">
             <input type="hidden" name="provider" value={{ provider|default:"local" }}>
diff --git a/snf-astakos-app/astakos/im/tests.py b/snf-astakos-app/astakos/im/tests.py
index dc7c457..d275550 100644
--- a/snf-astakos-app/astakos/im/tests.py
+++ b/snf-astakos-app/astakos/im/tests.py
@@ -516,8 +516,16 @@ class LocalUserTests(TestCase):
         data = {'email':'KPAP@grnet.gr', 'password1':'password',
                 'password2':'password', 'first_name': 'Kostas',
                 'last_name': 'Mitroglou', 'provider': 'local'}
-        r = self.client.post("/im/signup", data)
-        self.assertContains(r, messages.EMAIL_USED)
+        r = self.client.post("/im/signup", data, follow=True)
+        self.assertRedirects(r, reverse('index'))
+        self.assertContains(r, messages.NOTIFICATION_SENT)
+
+        # previous user replaced
+        user = AstakosUser.objects.get(pk=user.pk)
+        self.assertTrue(user.activation_sent)
+        self.assertFalse(user.email_verified)
+        self.assertFalse(user.is_active)
+        self.assertEqual(len(get_mailbox('kpap@grnet.gr')), 2)
 
         # hmmm, email exists; lets request a password change
         r = self.client.get('/im/local/password_reset')
diff --git a/snf-astakos-app/astakos/im/views.py b/snf-astakos-app/astakos/im/views.py
index 47e2094..7cfccd5 100644
--- a/snf-astakos-app/astakos/im/views.py
+++ b/snf-astakos-app/astakos/im/views.py
@@ -56,6 +56,7 @@ from django.http import (
 from django.shortcuts import redirect
 from django.template import RequestContext, loader as template_loader
 from django.utils.http import urlencode
+from django.utils.safestring import mark_safe
 from django.utils.translation import ugettext as _
 from django.views.generic.create_update import (
     apply_extra_context, lookup_object, delete_object, get_model_and_form_class)
@@ -607,7 +608,14 @@ def logout(request, template='registration/logged_out.html', extra_context=None)
         response['Location'] = LOGOUT_NEXT
         response.status_code = 301
     else:
-        messages.add_message(request, messages.SUCCESS, _(astakos_messages.LOGOUT_SUCCESS))
+        message = _(astakos_messages.LOGOUT_SUCCESS)
+        last_provider = request.COOKIES.get('astakos_last_login_method', None)
+        if last_provider:
+            provider = auth_providers.get_provider(last_provider)
+            extra_message = provider.get_logout_message_display
+            if extra_message:
+                message += '<br />' + extra_message
+        messages.add_message(request, messages.SUCCESS, mark_safe(message))
         response['Location'] = reverse('index')
         response.status_code = 301
     return response
