I'm trying to connect to an AD instance from my Ruby application. I've chosen LDAP for the job.
Below is my connection settings and script I've written.
def name_for_login( email, password )
email = email[/\A\w+/].downcase # Throw out the domain, if it was there
email << "#example.com" # I only check people in my company
ldap = Net::LDAP.new(
host: '10.0.0.2',
port: 1027,
auth: { method: :simple, email: email, password:password }
)
if ldap.bind
p 'lol'
# Yay, the login credentials were valid!
# Get the user's full name and return it
ldap.search(
base: "OU=Users,OU=Accounts,DC=example,DC=com",
filter: Net::LDAP::Filter.eq( "mail", email ),
attributes: %w[ displayName ],
return_result:true
).first.displayName.first
end
end
and test credentials:
windows : Windows#test
tester : Pass#123
If I run the script, it throws me the following error :
irb(main):025:0> name_for_login('tester','Pass#123')
Net::LDAP::BindingInformationInvalidError: Invalid binding information
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap/auth_adapter/simple.rb:14:in `bind'
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap/connection.rb:278:in `block in bind'
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap/instrumentation.rb:19:in `instrument'
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap/connection.rb:275:in `bind'
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap.rb:868:in `block in bind'
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap/instrumentation.rb:19:in `instrument'
from /Library/Ruby/Gems/2.3.0/gems/net-ldap-0.16.1/lib/net/ldap.rb:860:in `bind'
from (irb):9:in `name_for_login'
from (irb):25
from /usr/bin/irb:11:in `<main>'
I'm unsure from where to troubleshoot to understand the problem.
The AD is on a Windows server hosted on Azure.
I don't know Ruby, but my guess is that the problem is here:
auth: { method: :simple, email: email, password:password }
According to the documentation, you should be using the username property, not email. And you need to set it to either the username (sAMAccountName) of the account, or the userPrincipalName (which may be the same as the email address, or the distinguishedName.
Assuming the userPrincipalName is the same as the email address, then this might work:
auth: { method: :simple, username: email, password:password }
Related
I would like to know how to register a new user using AWS Cognito Ruby SDK.
So far I have tried:
Input
AWS_KEY = "MY_AWS_KEY"
AWS_SECRET = "MY_AWS_SECRET"
client = Aws::CognitoIdentityProvider::Client.new(
access_key_id: AWS_KEY,
secret_access_key: AWS_SECRET,
region: 'us-east-1',
)
resp = client.sign_up({
client_id: "4d2c7274mc1bk4e9fr******", # required
username: "test#test.com", # required
password: "Password23sing", # required
user_attributes: [
{
name: "app", # required
value: "my app name",
},
],
validation_data: [
{
name: "username", # required
value: "true",
},
]
})
Output
Aws::CognitoIdentityProvider::Errors::NotAuthorizedException (Unable to verify secret hash for client 4d2c7274mc1bk4e9fr*****)
References
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CognitoIdentityProvider/Client.html#sign_up-instance_method
If your app client is configured with a client secret, most of the client requests require you to include a 'secret hash' in the options parameters of the request. The Cognito docs describe the secret hash thusly:
The SecretHash value is a Base 64-encoded keyed-hash message
authentication code (HMAC) calculated using the secret key of a user
pool client and username plus the client ID in the message. The following pseudocode shows how this value is calculated.
Base64 ( HMAC_SHA256 ( "Client Secret Key", "Username" + "Client Id" ) )
The docs also make it clear via a glob of sample Java that you are expected to roll your own. After a bit of experimenting I was able to successfully complete a sign_up call with the following (my test pool was set up to require email and name attributes):
def secret_hash(client_secret, username, client_id)
Base64.strict_encode64(OpenSSL::HMAC.digest('sha256', CLIENT_SECRET, username + CLIENT_ID))
end
client = Aws::CognitoIdentityProvider::Client.new(
access_key_id: AWS_KEY,
secret_access_key: AWS_SECRET,
region: REGION)
username = 'bob.scum#example.com'
resp = client.sign_up({
client_id: CLIENT_ID,
username: username,
password: 'Password23sing!',
secret_hash: secret_hash(CLIENT_SECRET, username, CLIENT_ID),
user_attributes: [{ name: 'email', value: username },
{ name: 'name', value: 'Bob' }],
validation_data: [{ name: 'username', value: 'true' },
{ name: 'email', value: 'true' }]
})
CLIENT_SECRET is the app client secret that can be found under General Settings > App Clients.
Result:
#<struct Aws::CognitoIdentityProvider::Types::SignUpResponse
user_confirmed=false,
code_delivery_details=nil,
user_sub="c87c2ac8-1480-4d15-a28d-6998d9260e73">
I am trying to create a user via the Keycloak API, and I would like to assign a realm-level role to them when they are first added. However, it doesn't seem to work like the documentation says it should.
I know that I could simply make a second add-role-to-user API request after the initial create-user one, but:
The documentation indicates that I shouldn't need to do this.
The second API request could fail, leaving the user in an "incomplete" state.
It would make the code I'm writing more complex than it needs to be.
To test this in irb, using the keycloak Ruby gem, I first request an access token from Keycloak:
require 'keycloak'
json = Keycloak::Client.get_token_by_client_credentials
access_token = JSON.parse(json)['access_token']
All of the following create a user within Keycloak, but without the "owner" role:
Keycloak::Admin.generic_post('users', nil, { username: 'someone', realmRoles: ['owner'] }, access_token)
Keycloak::Admin.generic_post('users', nil, { username: 'someone', realmRoles: ['1fff5f5f-7357-4f73-b45d-65ccd01f3bc8'] }, access_token)
Keycloak::Admin.generic_post('users', nil, { username: 'someone', realmRoles: ['{"id":"1fff5f5f-7357-4f73-b45d-65ccd01f3bc8","name":"owner","description":"Indicates that a user is the owner of an organisation.","composite":false,"clientRole":false,"containerId":"MyRealmName"}'] }, access_token)
Attempting to use a role-hash instead of a string causes an error:
Keycloak::Admin.generic_post('users', nil, { username: 'someone', realmRoles: [{"id"=>"1fff5f5f-7357-4f73-b45d-65ccd01f3bc8", "name"=>"owner", "description"=>"Indicates that a user is the owner of an organisation.", "composite"=>false, "clientRole"=>false, "containerId"=>"MyRealmName"}] }, access_token)
Traceback (most recent call last):
16: from /home/thomas/.rvm/rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
15: from (irb):8
14: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/keycloak-3.0.0/lib/keycloak.rb:541:in `generic_post'
13: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/keycloak-3.0.0/lib/keycloak.rb:943:in `generic_request'
12: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/keycloak-3.0.0/lib/keycloak.rb:915:in `block in generic_request'
11: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient.rb:71:in `post'
10: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/request.rb:52:in `execute'
9: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/request.rb:145:in `execute'
8: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/request.rb:715:in `transmit'
7: from /home/thomas/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/net/http.rb:920:in `start'
6: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/request.rb:725:in `block in transmit'
5: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/request.rb:807:in `process_result'
4: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/keycloak-3.0.0/lib/keycloak.rb:916:in `block (2 levels) in generic_request'
3: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/keycloak-3.0.0/lib/keycloak.rb:958:in `rescue_response'
2: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/abstract_response.rb:103:in `return!'
1: from /home/thomas/.rvm/gems/ruby-2.6.3/gems/rest-client-2.0.2/lib/restclient/abstract_response.rb:223:in `exception_with_response'
RestClient::InternalServerError (500 Internal Server Error)
Keycloak prints the following, indicating that - as expected - the roles should be an array of strings, not hashes:
08:53:27,889 ERROR [org.keycloak.services.error.KeycloakErrorHandler] (default task-22) Uncaught server error: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
at [Source: (io.undertow.servlet.spec.ServletInputStreamImpl); line: 1, column: 37] (through reference chain: org.keycloak.representations.idm.UserRepresentation["realmRoles"]->java.util.ArrayList[0])
The same thing happens if I pass a single string instead of an array, like:
Keycloak::Admin.generic_post('users', nil, { username: 'someone', realmRoles: 'owner' }, access_token)
Am I doing something wrong, or is this simply a bug in the Keycloak API?
Reference
https://www.keycloak.org/docs-api/9.0/rest-api/index.html#_createuser
https://www.keycloak.org/docs-api/9.0/rest-api/index.html#_userrepresentation
Similar questions
Keycloak : unable to map user roles when creating user for api
Keycloak: roles not assigned when user is created via CLI
You did nothing wrong. It is a bug in the Keycloak API.
This request should work:
Keycloak::Admin.generic_post('users', nil, { username: 'someone', realmRoles: ['owner'] }, access_token)
Unfortunately the API documentation is wrong because the 'realmRoles' attribute doesn't work when trying to create/update a user/group.
You can find more informations about the behavior on the official bug tracker of Keycloak :
https://issues.jboss.org/browse/KEYCLOAK-3410
https://issues.jboss.org/browse/KEYCLOAK-10876
https://issues.jboss.org/browse/KEYCLOAK-5038
...
For now the only solution is to make multiple requests on the API, using the RoleMappers to map a role to a user.
Documentation about those operations : https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_role_mapper_resource
The bug is still present in keycloak 19.0.1 even tho was reported in 2016.
So there is a work around as GoGusto suggested. First create the user and then add the roles to the user.
private void addRealmRoleToUser(String username, String role){
UserRepresentation userRepresentation = keycloak.realm(REALM_NAME).users().search(username).get(0);
UserResource userResource =
keycloak.realm(REALM_NAME).users().get(userRepresentation.getId());
List<RoleRepresentation> rolesToAdd =
Arrays.asList(keycloak.realm(REALM_NAME).roles().get(TEST_ROLE).toRepresentation());
userResource.roles().realmLevel().add(rolesToAdd);
}
Respectively userRepresentation.getRealmRoles() is not working as well. getRealmRoles has to be done with UserResouce class like its written in keycloak UserTest.java:
RoleMappingResource userRoles = realm.users().get(userId).roles();
userRoles.realmLevel().add(Collections.singletonList(realm.roles().get("realm-composite").toRepresentation()));
userRoles.clientLevel(clientUuid).add(Collections
.singletonList(realm.clients().get(clientUuid).roles().get("client-composite").toRepresentation()));
// check state before making the direct assignments
assertNames(userRoles.realmLevel().listAll(), "realm-composite", Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
assertNames(userRoles.realmLevel().listAvailable(), "realm-child", "realm-role-in-group",
"admin", "customer-user-premium", "realm-composite-role",
"sample-realm-role",
"attribute-role", "user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION);
assertNames(userRoles.realmLevel().listEffective(), "realm-composite", "realm-child", "realm-role-in-group",
"user", "offline_access", Constants.AUTHZ_UMA_AUTHORIZATION,
Constants.DEFAULT_ROLES_ROLE_PREFIX + "-test");
I followed the quick start and am attempting to create a user using the google-api-ruby-client.
I've set up access in the google api console. And I can get this to work using the API explorer.
But when I try using the ruby client, I'm getting a resource not found: domain error.
Here's the code:
def self.create_user
# Initialize the client.
client = Google::APIClient.new(
:application_name => 'MYAPP',
:application_version => '0.0.1'
)
# Authorization
# Load our credentials for the service account
key = Google::APIClient::KeyUtils.load_from_pkcs12(KEY_FILE, KEY_SECRET)
client.authorization = Signet::OAuth2::Client.new(
token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
audience: 'https://accounts.google.com/o/oauth2/token',
scope: 'https://www.googleapis.com/auth/admin.directory.user',
issuer: ACCOUNT_ID,
signing_key: key)
# Request a token for our service account
client.authorization.fetch_access_token!
# Load API Methods
admin = client.discovered_api('admin', 'directory_v1')
# Make an API call.
result = client.execute(
admin.users.update,
name: { familyName: 'testy', givenName: 'testerson' },
password: '!password12345!',
primaryEmail: 'ttesterson#my-actual-domain.com'
)
result.data
end
Here's the response:
"error"=>{"errors"=>[{"domain"=>"global", "reason"=>"notFound", "message"=>"Resource Not Found: domain"}], "code"=>404, "message"=>"Resource Not Found: domain"}
Why?
After a bit of documentation reading, there were two things that I needed to fix.
I hadn't set up the proper authorization for my test service account.
You have to go to the Apps Console > Security > Advanced > Manage API client access and add the client url for your service account as well as any specific permissions that you want to add
As seen in this question, it seems that you need to create a user object rather than just passing in parameters.
Here's my updated code:
# Authorization happens here ....
api = client.discovered_api('admin', 'directory_v1')
new_user = api.users.insert.request_schema.new(
name: { familyName: 'Testy', givenName: 'Testerson' },
primaryEmail: 'ttttesterson#<domain-redacted>.com',
password: 'password123'
)
result = client.execute(
api_method: api.users.insert,
body_object: new_user
)
I am playing around with action mailer, and I don't understand a couple of things
class MYMailer < ActionMailer::Base
default from: "bar#gmail.com"
....
end
in my production.rb file:
#config.action_mailer.default_url_options = { :host => 'www.example.com' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 111,
domain: 'gmail.com',
user_name: 'foo#gmail.com',
password: 'foobar',
authentication: 'plain',
enable_starttls_auto: true }
1) First of all what is this default from: "bar#gmail.com" ?
The from value is read from my production file, and I receive emails from foo#gmail.com. So what is the point of default from: "bar#gmail.com"
2) Second what is the point of #config.action_mailer.default_url_options = { :host => 'www.example.com' } ? I read something on the official guides but I didn't get it. My app still sends email without it..
Thanks
1) Gmail Api Do not support sent email from different adress(protect from spam/spoof)
2) Rails can send email from self application and this options tell him what host use.
I'm trying to get an oauth token I can use with gmail_xauth (ruby gem)
to look at a user's mail. I first registered my app with google and
then set up devise to request access to mail:
config.omniauth :google, 'key', 'secret', :scope => 'https://mail.google.com/mail/feed/atom/'
I then go through the outh/openid flow and google prompts me to
approve access to gmail, redirecting me back to the app with a a token
and secret in the omniuth credentials & my Google account lists my app
as authorized to access my data. So far so good.
Now, when I take those credentials and try to use them with
gmail_xoauth like so:
require 'gmail_xoauth'
imap = Net::IMAP.new('imap.gmail.com', 993, usessl = true, certs =
nil, verify = false)
imap.authenticate('XOAUTH', '...#gmail.com',
:consumer_key => 'key,
:consumer_secret => 'secret',
:token => 'omniauth_returned_token',
:token_secret => 'omniauth_returned_secret'
)
I get an error "Net::IMAP::NoResponseError: Invalid credentials
(Failure)".
Interestingly, following the gmail_xoauth README to generate a token
with an same consumer using a python script it does work.
This works for me:
config.omniauth :google, 'anonymous', 'anonymous', :scope => 'https://mail.google.com/'
I'm using the gmail gem, so to connect it looks like this:
gmail = Gmail.connect(:xoauth, auth.uid,
:token => auth.token,
:secret => auth.secret,
:consumer_key => 'anonymous',
:consumer_secret => 'anonymous'
)
I'm passing an authentication object in, but you'll be getting it from the env variable env["omniauth.auth"]. I'm using anonymous/anonymous for the key/secret since I haven't registered my domain with google, but I believe you can here. It'll still work with anonymous/anonymous, but Google will just warn the user.
Google's OAuth1 protocol is now deprecated and many gems have not yet updated to use their OAuth2 protocol. Here is a working example of fetching email from Google using their OAuth2 protocol. This example uses the mail, gmail_xoauth, omniauth, and omniauth-google-oauth2 gems.
You will also need to register your app in Google's API console in order to get your API tokens.
# in an initializer:
ENV['GOOGLE_KEY'] = 'yourkey'
ENV['GOOGLE_SECRET'] = 'yoursecret'
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2, ENV['GOOGLE_KEY'], ENV['GOOGLE_SECRET'], {
scope: 'https://mail.google.com/,https://www.googleapis.com/auth/userinfo.email'
}
end
# ...after handling login with OmniAuth...
# in your script
email = auth_hash[:info][:email]
access_token = auth_hash[:credentials][:token]
imap = Net::IMAP.new('imap.gmail.com', 993, usessl = true, certs = nil, verify = false)
imap.authenticate('XOAUTH2', email, access_token)
imap.select('INBOX')
imap.search(['ALL']).each do |message_id|
msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822']
mail = Mail.read_from_string msg
puts mail.subject
puts mail.text_part.body.to_s
puts mail.html_part.body.to_s
end