gramex: Add additional user details post LDAP Auth and a conditional redirection - gramex

Consider a SQLite DB with user table having user_id, first_name, last_name, access_level columns.
After LDAP authentication, I'd like to update current_user object with additional metadata (from sql table) of the user_id.
Now, using this additional information (access_level included), user must be redirected to the view based on access permissions(access_level).

Let's say your sqlite database was called auth.db and was created like this:
$ sqlite3 auth.db
sqlite> CREATE TABLE user (user_id TEXT PRIMARY KEY, first_name TEXT, last_name TEXT, access_level TEXT);
sqlite> INSERT INTO user VALUES ('employee', 'Emp', 'Loyee', 'user');
sqlite> INSERT INTO user VALUES ('manager', 'Man', 'Ager', 'manager');
Construct your gramex.yaml like this:
url:
# Home page displays the current user object
home:
pattern: /$YAMLURL/
handler: FunctionHandler
kwargs:
function: json.dumps(handler.current_user)
# Login page uses LDAP auth
login:
pattern: /$YAMLURL/login
handler: LDAPAuth
kwargs:
# Let's connect to a test server. Login: employee. Password: Secret123
host: ipa.demo1.freeipa.org
use_ssl: true
user: 'uid={user},cn=users,cn=accounts,dc=demo1,dc=freeipa,dc=org'
password: '{password}'
# After the user logs in, send them to /login-redirect.
# This will update the current_user with additional metadata and redirect
redirect:
url: /$YAMLURL/login-redirect
# login-redirect sets the user object and redirects to relevant page
login-redirect:
pattern: /$YAMLURL/login-redirect
handler: FunctionHandler
kwargs:
function: mymodule.login_redirect(r'$YAMLPATH/auth.db', handler)
This relies on a mymodule.py which looks like this:
import sqlite3
import gramex.cache
import pandas as pd
def login_redirect(db, handler):
# The LDAP id is like uid=employee,cn=users,...
ldap_id = handler.current_user['id']
# Convert this into just the uid, which is "employee"
user_id = ldap_id.split(',')[0].split('=')[1]
# Look up the user's ID from the sqlite database
con = gramex.cache.open(db, sqlite3.connect)
result = pd.read_sql('SELECT * FROM user WHERE user_id=?', con, params=[user_id])
# Update the current user with the first entry (assuming it exists)
handler.current_user.update(result.iloc[0].to_dict())
# Redirect based on the access level
access_level = handler.current_user['access_level']
handler.redirect('.?employee-page' if access_level == 'user' else '.?invalid-page')
To try this:
Visit /login. you can log into the freeipa demo LDAP server with login employee and password Secret123
You will be redirected to /login-redirect. This looks up the user ID in auth.db, adds all other attributes into .current_user and redirects based on the access_level

Related

Omniauth-Saml with Devise - mapping and using an attribute

I'm pretty new to Rails but we have an app using Devise and Omniauth for authentication and have recently integrated Omniauth-Saml by following the Omniauth documentation for Devise integration: https://github.com/omniauth/omniauth-saml#devise-integration The authentication works and we can create users and use these accounts without any issues.
In the SAML response attributes is an lacode (4-digit string). We want to check this user attribute against a reference lacode. If their cag matches the reference cag we want to set the verified_at attribute in the user.rb model.
I've updated the user model and to test if I set the oauth_lacode to "9064" to match the oauth_lacode_ref then the code works and the user's verified_at time and date are set at point of account creation.
app/models/user.rb
# Get the existing user by email if the provider gives us a verified email.
def self.first_or_initialize_for_oauth(auth)
oauth_email = auth.info.email
oauth_email_confirmed = oauth_email.present? && (auth.info.verified || auth.info.verified_email)
oauth_lacode = auth.extra.raw_info.lacode
oauth_lacode_ref = "9064"
oauth_lacode_confirmed = oauth_lacode == oauth_lacode_ref
oauth_user = User.find_by(email: oauth_email) if oauth_email_confirmed
oauth_user || User.new(
username: auth.info.name || auth.uid,
email: oauth_email,
oauth_email: oauth_email,
password: Devise.friendly_token[0, 20],
terms_of_service: "1",
confirmed_at: oauth_email_confirmed ? DateTime.current : nil,
verified_at: oauth_lacode_confirmed ? DateTime.current : nil
)
end
I'm not mapping and calling the lacode from the hash correctly as I see this error in the log "NoMethodError (undefined method `lacode' for #OneLogin::RubySaml::Attributes:0x00007f7a5040ad40):"
This is how I'm mapping the attributes in config/initializers/devise.rb
attribute_statements: { email: ['urn:oid:0.9.2342.19200300.100.1.22'],
lacode: ['urn:oid:0.9.2342.19200300.100.1.17']}
I have confirmed with the IDP that 'urn:oid:0.9.2342.19200300.100.1.17' is mapped to the lacode in the SAML response.
As in the User model above, this is how I'm trying to access the lacode from within the User model.
"saml_cag = auth.extra.raw_info.lacode"
This is the guidance from Omniauth Saml:
:attribute_statements - Used to map Attribute Names in a SAMLResponse
to entries in the OmniAuth info hash. For example, if your
SAMLResponse contains an Attribute called 'EmailAddress', specify
{:email => ['EmailAddress']} to map the Attribute to the corresponding
key in the info hash. URI-named Attributes are also supported, e.g.
{:email =>
['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress']}.
Note: All attributes can also be found in an array under
auth_hash[:extra][:raw_info], so this setting should only be used to
map attributes that are part of the OmniAuth info hash schema.
Does this sentence at the end mean I don't need to/can't map the attribute. Can anyone help or point me in the right direction?
I managed to get this working. Only attribute names specified in the Omniauth Hash Schema can be used.
Mapping the lacode to description in the attribute statement, I was able to access it using "auth.info.description"

Can't use upsert on existing member

I'm trying to subscribe users to Mailchimp with Gibbon 2.2.4 with a generic subscribe method I've been using, and then shortly after I want to add in some extra fields to track the results of a quiz they took.
I want to store this data on Mailchimp because I'd like to manage the emails I send off directly from Mailchimp's dashboard.
The service I created to handle my subscriptions:
class MailchimpService
def subscribe(list_id,email,first_name)
GIBBON.lists(list_id).members.create({
body: {
email_address: email,
status: 'subscribed',
merge_fields: {
FNAME: first_name,
},
double_optin: false,
update_existing: true
}
})
end
def subscribe_to_quiz(first_name, email, user_id, quiz_id)
list_id = ENV['QUIZ_MAILCHIMP_LIST_ID']
if subscribe(list_id,email,first_name)
attempt = QuizAttempt.where("user_id = ? AND quiz_id = ?", user_id, quiz_id).last
correct = attempt.correct_answer_count
total = attempt.questions_answered
successful = attempt.successful?
send_quiz_results(list_id, email, correct, total, successful)
end
end
def send_quiz_results(list_id, email, correct, total, successful)
GIBBON.lists(list_id).members(email).upsert(
body: {
email_address: email,
status: 'subscribed',
merge_fields: {
correct_answers: correct,
total_answers: total,
successful: successful
},
update_existing: true
})
end
end
In subscribe_to_quiz, I'm subscribing the user to my quiz_list in Mailchimp. The values of the fields I'm updating here are irrelevant, but I think they're quite explanatory. When I try to run my upsert statement in send_quiz_results, I get the following error:
the server responded with status 400
#title="Member Exists",
#detail="foo#bar.baz is already a list member. Use PUT to insert or update list members.",
#body={"type"=>"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/", "title"=>"Member Exists", "status"=>400, "detail"=>"foo#bar.baz is already a list member. Use PUT to insert or update list members.", "instance"=>""},
#raw_body="{\"type\":\"http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/\",\"title\":\"Member Exists\",\"status\":400,\"detail\":\"foo#bar.baz is already a list member. Use PUT to insert or update list members.\",\"instance\":\"\"}",
#status_code=400
I have no clue why it won't let me do this... It seems like it's referencing a create statement, but the extracted source for the error references my upsert statement.
I know I'm using the corresponding PUT verb for Gibbon, since the following is taken straight from the documentation:
Of course, body is only supported on create, update, and upsert calls. Those map to HTTP POST, PATCH, and PUT verbs respectively.
I have no clue why this isn't working... I've tried taking out the other fields and just simply throwing in the ones I'm updating. I've also tried running it straight from the terminal to make sure nothing's overlapping.
The MailChimp API docs show that when updating a member you must provide the member's subscriber_hash, which the MD5 hash of the lowercase version of the members email address.
Use Digest::MD5.hexdigest to hash the email address with MD5:
GIBBON.lists(list_id).members(Digest::MD5.hexdigest(email.downcase)).upsert

Hash username and password for authenticate ruby on rails

How can l implement a hash function (SHA512) that encrypt the username and check if this username and password is correct in the SQL server?
Working login right now if you set a cleartext username
user = User.find_by(username: params[:session][:username].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
redirect_to :root
else
flash.now[:warning] = 'Wrong username/password'
render 'new'
end
I have tried encrypt the username before it runs the if statement but I can get it to work.
The way I hash the username is:
username = params[:session][:username].downcase
username_encrypted = Digest::SHA2.new(512).hexdigest(username)
any ideas how this can be made?
What i am trying to achieve with this is. Too have one column in SQL with encrypted usernames and another column with "public" usernames. Where public usernames is visible for everybody on the site and encrypted usernames is stored in SQL only for authentication.

Django admin - Email username and password on user creation

I'm using django-1.4.3 and the superuser can create users and assign password to them. Here I'm using django-email-as-username to enable users to login with their email as username. When the superuser adds a new user, the newly added user should be notified through email with his username and password.
I'm able to send email after user creation using post_save signal. But I couldn't get the password as it will be encrypted and stored. I want to email the user, the raw password. How can I achieve this?
I have achieved it using the code below:
from django.db.models.signals import post_save
from django.contrib.auth.models import User
from django.dispatch import receiver
#receiver(post_save, sender = User)
def my_callback(sender, **kwargs):
import inspect
records =[]
for frame_record in inspect.stack():
records.append(frame_record[3])
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
email = request.POST.get('email')
password1 = request.POST.get('password1')
password2 = request.POST.get('password2')
if email != None and password1 != None and password2 != None and password1 == password2:
html_content ="Hi,<br> Your username: %s <br> Password: %s"
from_email = settings.DEFAULT_FROM_EMAIL
message = EmailMessage('Welcome', html_content %(email, password1), from_email, [email])
message.content_subtype = "html" # Main content is now text/html
message.send()
break

Update all properties in list using linq

I need to update all the properties in a list object using linq.
For ex.: I have an User List with (Name, Email, PhoneNo,...) as properties.
I will get the Users List(List<Users>) from database which is filled with all properties except Email. I need to update all the Email property in the list after retrieving from database with some email in session.
How can i do it?
You should be able to do it simply via ForEach..
users.ForEach(user => user.email = sessionEmail);
or for multiple properties..
users.ForEach(user =>
{
user.email = sessionEmail;
user.name = "Some Person";
...
});
If I understood correctly, you want to retrieve all the users who have no email assigned. Then, what about something like this:
var users=dataContext.Users.Where(user => user.email==null).ToList();
foreach(var user in users) {
user.Email="some#email.com"; //Or, choose a different email for each user
}
And if you want to update the users information back to the database:
dataContext.SubmitChanges();

Resources