Reading Keycloak OmniAuth::AuthHash elements with Ruby 2.7 - ruby

From the Keycloak authentication token, I read the OmniAuth::AuthHash elements to extract the user's name, email and roles.
Reading the name and email are quite easy based on the token retrieved through auth = request.env["omniauth.auth"] statement.
Digging into the token's hierarchy provides requested information:
user.name = auth.info.name
user.uuid = auth.uid
user.provider = auth.provider
user.email = auth.info.email
I use the same method to search for user's roles list:
roles = auth.extra.raw_info.resource_access provides the following AuthHash:
#<OmniAuth::AuthHash
BFS.SIS=#<OmniAuth::AuthHash
roles=#<Hashie::Array
["dataproducer",
"fsodataconsumer",
"sisdatasteward"]
>
>
BFS.SIS.DAL=#<OmniAuth::AuthHash
roles=#<Hashie::Array
["kd_getLoadReports",
"kd_createTables",
"kd_readTables",
"kd_deleteTables"]
>
>
BFS.SIS.DPS.KEYSTORE=#<OmniAuth::AuthHash
roles=#<Hashie::Array
["keymanagement_key_read",
"keymanagement_keystore_read"]
>
>
BFS.SIS.SMS=#<OmniAuth::AuthHash
roles=#<Hashie::Array
["kdDatasetInformation_read",
"codeLists_update",
"definedVariables_set_status_validation_in_progress",
"hierarchicalCodeLists_update",
"hierarchicalCodeLists_create",
"kdDatasetInformation_delete",
"kdDatasetInformation_update",
"kdDataStructureDefinitions_create",
"kdDataStructureDefinitions_update",
"kdDataStructureDefinitions_delete",
"kdDataStructureDefinitions_read",
"kdDatasetInformation_create",
"definedVariables_set_status_open_from_rejected"]
>
>
BFS.SIS.UI=#<OmniAuth::AuthHash
roles=#<Hashie::Array
["bfs.sis.portal"]
>
>
>
There is the issue: as key names contain a '.', I cannot continue accessing subkeys with the syntax key.subkey to retrieve the array of roles for the BFS.SIS and BFS.SIS.SMS keys.
How could I extract the arrays from these keys?
Thanks for your help!

Well, I can't dig into the hash key.subkey syntax, but I can enumerate the subkeys. And then I can check if some match with needed entries, and extract child roles.
Here is the solution I implemented:
auth = request.env["omniauth.auth"]
roles = Array.new
activities = auth.extra.raw_info.allowlists.statisticalActivities
resources_accesses = auth.extra.raw_info.resource_access
resources_accesses.each do |access|
puts access # Provides the resources_access hash
puts access[0] # Provides the resources_access label
puts access[1] # Provides the resources_access roles array
# Check if label matches needed entries
if ["BFS.SIS.SMS", "BFS.SIS", "BFS.SIS.SCHEDULER"].include? access[0].to_s
access[1].roles.each do |role|
# Store each role in the roles array
roles << role
end
end
end
This list of roles for a user will help building the list of abilities for CanCanCan gem.

Related

where the 'attr' value of the validate of SocialLoginSerializer is output in django rest framework

I wonder where the attrs of validate in drf's SocialLoginSerializer are expressed.
Actually, I want to change the response values ​​when I do social login,
but I don't know where to change them.
Please help me
class SocialLoginSerializer(serializers.Serializer):
...
def validate(self, attrs):
...
if not login.is_existing:
# We have an account already signed up in a different flow
# with the same email address: raise an exception.
# This needs to be handled in the frontend. We can not just
# link up the accounts due to security constraints
if allauth_settings.UNIQUE_EMAIL:
# Do we have an account already with this email address?
account_exists = get_user_model().objects.filter(
email=login.user.email,
).exists()
if account_exists:
raise serializers.ValidationError(
_('User is already registered with this e-mail address.'),
)
login.lookup()
login.save(request, connect=True)
return attrs

How to search roles in ldap recursively with Net::LDAP in Ruby

I'm creating a self service with the possibility to grant application roles (defined in a meta [ldap]) for a user. Our structure in the meta is not uniform. It looks like this:
o=meta
ou=Firm
ou=AppRoles
ou=GitLab
cn=Admin
cn=User
ou=SAP
ou=SAPCRT
cn=Admin
cn=User
ou=SAPLST
ou=NW
cn=Admin
cn=User
ou=ST
cn=Admin
cn=User
etc...
So you see, the cn (Approle) is not always on the same level.
This is the code I have so far. It finds 'ou's like GitLab Admin and GitLab User. But I need to receive a list with Gitlab Admin, Gitlab User, SAP/SAPCRT Admin, SAP/SAPCRT User, SAP/SAPLST/NW Admin, and so forth.
base = 'ou=AppRoles,ou=Firm,o=META'
filter = Net::LDAP::Filter.begins('ou', query)
How can I setup Net::LDAP to filter/search recursively?
Not sure if this will print all cn's under AppRole, but with the "puts" command you will see the output, could you show us the return of this block of code?
def get_ldap_users(ldap_password)
filter = Net::LDAP::Filter.eq("ou", "AppRoles")
treebase = "dc=yourdomainhere"
get_ldap(ldap_password).search(:base => treebase, :filter => filter) do |entry|
puts "CN: #{entry.cn}"
end
end

Can not get signed in email using Office 365 REST API

I followed this post http://dev.office.com/code-samples-detail/2142 and Ruby to get user's email address. Here is the code:
# Parses an ID token and returns the user's email
def get_email_from_id_token(id_token)
# JWT is in three parts, separated by a '.'
token_parts = id_token.split('.')
# Token content is in the second part
encoded_token = token_parts[1]
# It's base64, but may not be padded
# Fix padding so Base64 module can decode
leftovers = token_parts[1].length.modulo(4)
if leftovers == 2
encoded_token += '=='
elsif leftovers == 3
encoded_token += '='
end
# Base64 decode (urlsafe version)
decoded_token = Base64.urlsafe_decode64(encoded_token)
# Load into a JSON object
jwt = JSON.parse(decoded_token)
# Email is in the 'preferred_username' field
email = jwt['preferred_username']
end
This function worked very well, I can get user's email address. But today, this function still works without error but the JSON I got not contain user's email address anymore.
Could someone help me? I want to get user's email address. Thank you !
Azure deployed a breaking change to the v2 app model, and you don't get user info by default anymore.
You can read all about it here: https://azure.microsoft.com/en-us/documentation/articles/active-directory-v2-preview-oidc-changes/, but to summarize:
The openid scope used to give you basic profile info for the user.
That wasn't in line with the OpenID standard
Azure changed to require that you request the profile scope to get access to that information
For that sample, find this bit:
# Scopes required by the app
SCOPES = [ 'openid',
'https://outlook.office.com/mail.read' ]
And change it to:
# Scopes required by the app
SCOPES = [ 'openid',
'profile',
'https://outlook.office.com/mail.read' ]
Please add profile and email in your scope :
SCOPES = [ 'openid',
'profile',
'email',
'https://outlook.office.com/mail.read' ]

Generate expiring activator token or a key hash in rails manually

I'm trying to verify a link that will expire in a week. I have an activator_token stored in the database, which will be used to generate the link in this format: http://www.example.com/activator_token. (And not activation tokens generated by Devise or Authlogic.)
Is there a way to make this activator token expire (in a week or so) without comparing with updated_at or some other date. Something like an encoded token, which will return nil when decoded after a week. Can any existing modules in Ruby do this? I don't want to store the generated date in the database or in an external store like Redis and compare it with Time.now. I want it to be very simple, and wanted to know if something like this already exists, before writing the logic again.
What you want to use is: https://github.com/jwt/ruby-jwt .
Here is some boilerplate code so you can try it out yourself.
require 'jwt'
# generate your keys when deploying your app.
# Doing so using a rake task might be a good idea
# How to persist and load the keys is up to you!
rsa_private = OpenSSL::PKey::RSA.generate 2048
rsa_public = rsa_private.public_key
# do this when you are about to send the email
exp = Time.now.to_i + 4 * 3600
payload = {exp: exp, discount: '9.99', email: 'user#example.com'}
# when generating an invite email, this is the token you want to incorporate in
# your link as a parameter
token = JWT.encode payload, rsa_private, 'RS256'
puts token
puts token.length
# this goes into your controller
begin
#token = params[:token]
decoded_token = JWT.decode token, rsa_public, true, { :algorithm => 'RS256' }
puts decoded_token.first
# continue with your business logic
rescue JWT::ExpiredSignature
# Handle expired token
# inform the user his invite link has expired!
puts "Token expired"
end

Ruby: Dynamically defining classes based on user input

I'm creating a library in Ruby that allows the user to access an external API. That API can be accessed via either a SOAP or a REST API. I would like to support both.
I've started by defining the necessary objects in different modules. For example:
soap_connecton = Library::Soap::Connection.new(username, password)
response = soap_connection.create Library::Soap::LibraryObject.new(type, data, etc)
puts response.class # Library::Soap::Response
rest_connecton = Library::Rest::Connection.new(username, password)
response = rest_connection.create Library::Rest::LibraryObject.new(type, data, etc)
puts response.class # Library::Rest::Response
What I would like to do is allow the user to specify that they only wish to use one of the APIs, perhaps something like this:
Library::Modes.set_mode(Library::Modes::Rest)
rest_connection = Library::Connection.new(username, password)
response = rest_connection.create Library::LibraryObject.new(type, data, etc)
puts response.class # Library::Response
However, I have not yet discovered a way to dynamically set, for example, Library::Connection based on the input to Library::Modes.set_mode. What would be the best way to implement this functionality?
Murphy's law prevails; find an answer right after posting the question to Stack Overflow.
This code seems to have worked for me:
module Library
class Modes
Rest = 1
Soap = 2
def self.set_mode(mode)
case mode
when Rest
Library.const_set "Connection", Class.new(Library::Rest::Connection)
Library.const_set "LibraryObject", Class.new(Library::Rest::LibraryObject)
when Soap
Library.const_set "Connection", Class.new(Library::Soap::Connection)
Library.const_set "LibraryObject", Class.new(Library::Soap::LibraryObject)
else
throw "#{mode.to_s} is not a valid Library::Mode"
end
end
end
end
A quick test:
Library::Modes.set_mode(Library::Modes::Rest)
puts Library::Connection.class == Library::Rest::Connection.class # true
c = Library::Connection.new(username, password)

Resources