stormpath-sdk 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 30a7563696f87d582d6d3f31cc26613ba7730aeb
4
- data.tar.gz: 3587598dc7a6959997b14379b6b291fa9a9eac5b
3
+ metadata.gz: 1d31ad099a140c12a0522f4bca201aebb81838e4
4
+ data.tar.gz: b1de03018cc6bf723c873dd3c8368cdb197d3ac8
5
5
  SHA512:
6
- metadata.gz: 080f49f98c6119fe238f9d1e8bb48377a3c1fb14d28b9e72852c571d642c73d6383e7a52efd1c792f25bea88bc8e951704d371ba755488fee2167a1b819fed9e
7
- data.tar.gz: 07b8ddd35409fd0ba95a01838e9f8889f40015ff13995e0495561154079e96d50ea771ad0468a9d94c605ebfc55c002c0174e14d96d9b63337e25617bb866afd
6
+ metadata.gz: a4829ff4c1ac11afff95f83ae2f7adda7ca796bd13369a45635496607da558b6c10e3ca6f8e0e4ec38e7930a3d62b796aad461e157de216381644f3705a60c44
7
+ data.tar.gz: a437e1bf41aa13588ae014d9dcc9f3730537e31558b2e1b268ca92052098f747228d7f634714b6c9d766e04996cc89a202dade529d6fe9c11d254d0e483dcd56
data/.travis.yml CHANGED
@@ -1,8 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
- - 2.0.0
5
- - 2.1.2
3
+ - 2.1.5
4
+ - 2.2.4
5
+ - 2.3.0
6
6
  services:
7
7
  - redis-server
8
8
  before_install:
data/CHANGES.md CHANGED
@@ -1,6 +1,17 @@
1
1
  stormpath-sdk-ruby Changelog
2
2
  ============================
3
3
 
4
+ Version 1.1.0
5
+ -------------
6
+
7
+ Released on May 11, 2016
8
+
9
+ - Saml integration
10
+ - Support for specifying an organization name_key on login attempts
11
+ - Support for specifying the account store when reseting passwords
12
+ - Add delegate to organizations
13
+ - Add Organization docs to README
14
+
4
15
  Version 1.0.1
5
16
  -------------
6
17
 
data/README.md CHANGED
@@ -304,7 +304,7 @@ application.create_id_site_url({
304
304
  });
305
305
  ```
306
306
 
307
- ##### Using Subdomains
307
+ ##### Using Subdomains
308
308
 
309
309
  In some cases, you may want to show the organization that the user is logging into as a subdomain instead of an form field. To configure this, you need to use a [wildcard certificate][wildcard-certificate] when setting up your [custom domain with ID Site][custom-domain-with-id-site]. Otherwise, the Stormpath infrastructure will cause browser SSL errors.
310
310
 
@@ -379,8 +379,8 @@ Again, with all these methods, You will want your application to link to an inte
379
379
  > A JWT will expire after 60 seconds of creation.
380
380
 
381
381
  #### Exchange ID Site token for a Stormpath Access Token
382
- After the user has been authenticated via ID Site, a developer may want to control their authorization with an OAuth 2.0 Token.
383
- This is done by passing the JWT similar to the way we passed the user’s credentials as described in [Generating an OAuth 2.0 Access Token][generate-oauth-access-token].
382
+ After the user has been authenticated via ID Site, a developer may want to control their authorization with an OAuth 2.0 Token.
383
+ This is done by passing the JWT similar to the way we passed the user’s credentials as described in [Generating an OAuth 2.0 Access Token][generate-oauth-access-token].
384
384
  The difference is that instead of using the password grant type and passing credentials, we will use the id_site_token type and pass the JWT we got from the ID Site
385
385
  more info [here][exchange-id-site-token].
386
386
 
@@ -486,6 +486,204 @@ rescue Stormpath::Error => e
486
486
  end
487
487
  ```
488
488
 
489
+ The `UsernamePasswordRequest` can take an optional link to the application’s accountStore (directory or group) or the Organization nameKey. Specifying this attribute can speed up logins if you know exactly which of the application’s assigned account stores contains the account: Stormpath will not have to iterate over the assigned account stores to find the account to authenticate it. This can speed up logins significantly if you have many account stores (> 15) assigned to the application.
490
+
491
+ The `UsernamePasswordRequest` can receive the AccountStore in three ways.
492
+
493
+ Passing the organization, directory or group instance:
494
+
495
+ ```ruby
496
+ auth_request =
497
+ Stormpath::Authentication::UsernamePasswordRequest.new 'johnsmith', '4P@$$w0rd!', account_store: organization
498
+ ```
499
+
500
+ Passing the organization, directory or group href:
501
+
502
+ ```ruby
503
+ auth_request =
504
+ Stormpath::Authentication::UsernamePasswordRequest.new 'johnsmith', '4P@$$w0rd!', account_store: { href: organization.href }
505
+ ```
506
+
507
+ Passing the organization name_key:
508
+
509
+ ```ruby
510
+ auth_request =
511
+ Stormpath::Authentication::UsernamePasswordRequest.new 'johnsmith', '4P@$$w0rd!', account_store: { name_key: organization.name_key }
512
+ ```
513
+
514
+ ### Authentication Against a SAML Directory
515
+
516
+ SAML is an XML-based standard for exchanging authentication and authorization data between security domains.
517
+ Stormpath enables you to allow customers to log-in by authenticating with an external SAML Identity Provider.
518
+
519
+ #### Stormpath as a Service Provider
520
+
521
+ The specific use case that Stormpath supports is user-initiated single sign-on. In this scenario, a user requests
522
+ a protected resource (e.g. your application). Your application, with the help of Stormpath, then confirms the users
523
+ identity in order to determine whether they are able to access the resource. In SAML terminology, the user is the User
524
+ Agent, your application (along with Stormpath) is the Service Provider, and the third-party SAML authentication site
525
+ is the Identity Provider or IdP.
526
+
527
+ The broad strokes of the process are as follows:
528
+
529
+ * User Agent requests access from Service Provider
530
+ * Service Provider responds with redirect to Identity Provider
531
+ * Identity Provider authenticates the user
532
+ * Identity provider redirects user back to Service Provider along with SAML assertions.
533
+ * Service Provider receives SAML assertions and either creates or retrieves Account information
534
+
535
+ #### Configuring Stormpath as a Service Provider
536
+
537
+ Configuration is stored in the Directory's Provider resource. Here we will explain to you the steps that are required to configure Stormpath as a SAML Service
538
+ Provider.
539
+
540
+ #### Step 1: Gather IDP Data
541
+
542
+ You will need the following information from your IdP:
543
+
544
+ - **SSO Login URL** - The URL at the IdP to which SAML authentication requests should be sent. This is often called an "SSO URL", "Login URL" or "Sign-in URL".
545
+ - **SSO Logout URL** - The URL at the IdP to which SAML logout requests should be sent. This is often called a "Logout URL", "Global Logout URL" or "Single Logout URL".
546
+ - **Signing Cert** - The IdP will digitally sign auth assertions and Stormpath will need to validate the signature. This will usually be in .pem or .crt format, but Stormpath requires the text value.
547
+ - **Signing Algorithm** - You will need the name of the signing algorithm that your IdP uses. It will be either "RSA-SHA256" or "RSA-SHA1".
548
+
549
+ #### Step 2: Configure Your SAML Directory
550
+
551
+ Input the data you gathered in Step 1 above into your Directory's Provider resource, and then pass that along as part of the Directory creation HTTP POST:
552
+
553
+ ```ruby
554
+ provider = "saml"
555
+ request = Stormpath::Provider::AccountRequest.new(provider, :access_token, access_token)
556
+ application.get_provider_account(request)
557
+
558
+ client.directories.create(
559
+ name: "infinum_directory",
560
+ description: "random description",
561
+ provider: {
562
+ provider_id: "saml"
563
+ }
564
+ )
565
+ ```
566
+
567
+ to get the data about the provider just type
568
+ ```ruby
569
+ directory.provider
570
+ ```
571
+
572
+ provider method returns instance of SamlProvider and you can access the following data
573
+
574
+ ```ruby
575
+ dir_provider = directory.provider
576
+
577
+ dir_provider.provider_id
578
+ dir_provider.sso_login_url
579
+ dir_provider.sso_logout_url
580
+ dir_provider.encoded_x509_signing_cert
581
+ dir_provider.request_signature_algorithm
582
+ dir_provider.service_provider_metadata
583
+ dir_provider.attribute_statement_mapping_rules
584
+ dir_provider.creted_at
585
+ dir_provider.modified_at
586
+ ```
587
+
588
+ ##### Retrieve Your Service Provider Metadata
589
+
590
+ Next you will have to configure your Stormpath-powered application as a Service Provider in your Identity Provider. This means that you will need to retrieve the correct metadata from Stormpath.
591
+
592
+ In order to retrieve the required values, start by sending a GET to the Directory's Provider:
593
+
594
+ ```ruby
595
+ directory.provider_metadata
596
+ ```
597
+
598
+ provider_metadata method returns instance of SamlProviderMetadata and you can access the following values
599
+
600
+ ```ruby
601
+ dir_provider_metadata = directory.provider_metadata
602
+
603
+ dir_provider_metadata.href
604
+ dir_provider_metadata.entity_id
605
+ dir_provider_metadata.x509_signing_cert
606
+ dir_provider_metadata.assertion_consumer_service_post_endpoint
607
+ dir_provider_metadata.created_at
608
+ dir_provider_metadata.modified_at
609
+ ```
610
+
611
+ From this metadata, you will need two values:
612
+
613
+ - **Assertion Consumer Service URL**: This is the location the IdP will send its response to.
614
+ - **X509 Signing Certificate**: The certificate that is used to sign the requests sent to the IdP. If you retrieve XML, the certificate will be embedded. If you retrieve JSON, you'll have to follow a further /x509certificates link to retrieve it.
615
+
616
+ You will also need two other values, which will always be the same:
617
+
618
+ - **SAML Request Binding**: Set to HTTP-Redirect.
619
+ - **SAML Response Binding**: Set to HTTP-Post.
620
+
621
+ #### Step 4: Configure Your Service Provider in Your Identity Provider
622
+
623
+ Log-in to your Identity Provider (Salesforce, OneLogin, etc) and enter the information you retrieved in
624
+ the previous step into the relevant application configuration fields. The specific steps to follow here
625
+ will depend entirely on what Identity Provider you use, and for more information you should consult your
626
+ Identity Provider's SAML documentation.
627
+
628
+ #### Step 5: Configure Your Application
629
+
630
+ The Stormpath `Application` Resource has two parts that are relevant to SAML:
631
+
632
+ - an ``authorizedCallbackUri`` Array that defines the authorized URIs that the IdP can return your user to. These should be URIs that you host yourself.
633
+ - an embedded ``samlPolicy`` object that contains information about the SAML flow configuration and endpoints.
634
+
635
+ ```ruby
636
+ application.authorized_callback_uris = ["https://myapplication.com/whatever/callback", "https://myapplication.com/whatever/callback2"]
637
+ application.save
638
+ ```
639
+
640
+ #### Step 6: Add the SAML Directory as an Account Store
641
+
642
+ The next step is to map the new Directory to your Application with an Account Store Mapping.
643
+
644
+ ##### Step 7: Configure SAML Assertion Mapping
645
+
646
+ The Identity Provider's SAML response contains assertions about the user's identity, which Stormpath can use to create and populate a new Account resource.
647
+
648
+ ```xml
649
+ <saml:AttributeStatement>
650
+ <saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
651
+ <saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
652
+ </saml:Attribute>
653
+ <saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
654
+ <saml:AttributeValue xsi:type="xs:string">jane@example.com</saml:AttributeValue>
655
+ </saml:Attribute>
656
+ <saml:Attribute Name="location" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
657
+ <saml:AttributeValue xsi:type="xs:string">Tampa, FL</saml:AttributeValue>
658
+ </saml:Attribute>
659
+ </saml:AttributeStatement>
660
+
661
+ ```
662
+ The Attribute Assertions (`<saml:AttributeStatement>`) are brought into Stormpath and become Account and customData attributes.
663
+
664
+ SAML Assertion mapping is defined in an **attributeStatementMappingRules** object found inside the Directory's Provider object, or directly: `/v1/attributeStatementMappingRules/$RULES_ID`.
665
+
666
+ ##### Mapping Rules
667
+
668
+ The rules have three different components:
669
+
670
+ - **name**: The SAML Attribute name
671
+ - **nameFormat**: The name format for this SAML Attribute, expressed as a Uniform Resource Name (URN).
672
+ - **accountAttributes**: This is an array of Stormpath Account or customData (`customData.$KEY_NAME`) attributes that will map to this SAML Attribute.
673
+
674
+ In order to create the mapping rules, we simply send the following:
675
+
676
+ ```ruby
677
+ mappings = Stormpath::Provider::SamlMappingRules.new(items: [
678
+ {
679
+ name: "uid",
680
+ account_attributes: ["username"]
681
+ }
682
+ ])
683
+
684
+ dir.create_attribute_mappings(mappings)
685
+ ```
686
+
489
687
  ### Password Reset
490
688
 
491
689
  A password reset workflow, if configured on the directory the account is
@@ -576,6 +774,39 @@ Group membership can be created by:
576
774
 
577
775
  You will need to reload the account or group resource after these
578
776
  operations to ensure they've picked up the changes.
777
+
778
+ ### Working with Organizations
779
+
780
+ An `Organization` is a top-level container for Account Stores. You can think of an Organization as a tenant for your multi-tenant application. This is different than your Stormpath Tenant, which represents your tenant in the Stormpath system. Organizations are powerful because you can group together account stores that represent a tenant.
781
+
782
+ * Locate an organization
783
+
784
+ ```ruby
785
+ client.organizations.search(name: 'Finance Organization')
786
+ ```
787
+
788
+ * Create an organization
789
+
790
+ ```ruby
791
+ client.organizations.create(name: 'Finance Organization', name_key: 'finance-organization')
792
+ ```
793
+
794
+ * Adding an account store to an organization
795
+
796
+ ```ruby
797
+ client.organization_account_store_mappings.create(
798
+ account_store: { href: directory_or_group.href },
799
+ organization: { href: organization.href }
800
+ )
801
+ ```
802
+
803
+ * Adding an Organization to an Application as an Account Store
804
+
805
+ ```ruby
806
+ client.account_store_mappings.create application: application, account_store: organization
807
+ ```
808
+
809
+
579
810
  ### Add Custom Data to Accounts or Groups
580
811
 
581
812
  Account and Group resources have predefined fields that are useful to many applications, but you are likely to have your own custom data that you need to associate with an account or group as well.
data/Rakefile CHANGED
@@ -1,9 +1,11 @@
1
+ require 'bundler/gem_tasks'
1
2
  require 'rubygems'
2
3
  require 'rubygems/package_task'
3
4
  require 'rspec/core/rake_task'
4
5
  require 'stormpath-sdk'
5
6
  require './support/api'
6
7
 
8
+
7
9
  spec = eval(File.read('stormpath-sdk.gemspec'))
8
10
 
9
11
  Gem::PackageTask.new(spec) do |p|
@@ -27,4 +29,4 @@ namespace :api do
27
29
  ENV['STORMPATH_SDK_TEST_DIRECTORY_WITH_VERIFICATION_URL']
28
30
  )
29
31
  end
30
- end
32
+ end
data/lib/stormpath-sdk.rb CHANGED
@@ -87,6 +87,10 @@ module Stormpath
87
87
  autoload :LinkedinProviderData, 'stormpath-sdk/provider/linkedin/linkedin_provider_data'
88
88
  autoload :GithubProvider, 'stormpath-sdk/provider/github/github_provider'
89
89
  autoload :GithubProviderData, 'stormpath-sdk/provider/github/github_provider_data'
90
+ autoload :SamlProvider, 'stormpath-sdk/provider/saml/saml_provider'
91
+ autoload :SamlProviderData, 'stormpath-sdk/provider/saml/saml_provider_data'
92
+ autoload :SamlProviderMetadata, 'stormpath-sdk/provider/saml/saml_provider_metadata'
93
+ autoload :SamlMappingRules, 'stormpath-sdk/provider/saml/saml_mapping_rules'
90
94
  autoload :StormpathProvider, 'stormpath-sdk/provider/stormpath/stormpath_provider'
91
95
  autoload :StormpathProviderData, 'stormpath-sdk/provider/stormpath/stormpath_provider_data'
92
96
  end
@@ -28,6 +28,10 @@ module Stormpath
28
28
  def account_store=(account_store)
29
29
  if account_store.kind_of? Stormpath::Resource::Base
30
30
  set_property ACCOUNT_STORE, {HREF_PROP_NAME => account_store.href}
31
+ elsif account_store.kind_of? Hash
32
+ set_property ACCOUNT_STORE, sanitize(account_store)
33
+ else
34
+ fail ArgumentError, 'account_store should be a Stormpath::Resource::Instance or a Hash'
31
35
  end
32
36
  end
33
37
 
@@ -46,4 +46,3 @@ module Stormpath
46
46
  end
47
47
  end
48
48
  end
49
-
@@ -53,7 +53,7 @@ module Stormpath
53
53
  data_store.save token, Stormpath::Resource::Account
54
54
  end
55
55
  end
56
- has_many :organizations, href: '/organizations', can: [:get, :create]
56
+ has_many :organizations, href: '/organizations', can: [:get, :create], delegate: true
57
57
  has_many :groups, href: '/groups', can: :get
58
58
  has_many :group_memberships, href: '/groupMemberships', can: [:get, :create]
59
59
  has_many :account_store_mappings, href: '/accountStoreMappings', can: [:get, :create]
@@ -119,7 +119,7 @@ class Stormpath::DataStore
119
119
 
120
120
  request = Request.new(http_method, href, query, Hash.new, body, @api_key)
121
121
 
122
- if resource.try(:form_data?)
122
+ if resource.try(:form_data?)
123
123
  apply_form_data_request_headers request
124
124
  else
125
125
  apply_default_request_headers request
@@ -141,7 +141,7 @@ class Stormpath::DataStore
141
141
 
142
142
  return if http_method == 'delete'
143
143
 
144
- if result[HREF_PROP_NAME]
144
+ if result[HREF_PROP_NAME] and !resource_is_saml_mapping_rules? resource
145
145
  cache_walk result
146
146
  else
147
147
  result
@@ -213,7 +213,7 @@ class Stormpath::DataStore
213
213
  request.http_headers.store 'Content-Type', 'application/json'
214
214
  end
215
215
  end
216
-
216
+
217
217
  def apply_form_data_request_headers(request)
218
218
  request.http_headers.store 'Content-Type', 'application/x-www-form-urlencoded'
219
219
  apply_default_user_agent(request)
@@ -277,7 +277,7 @@ class Stormpath::DataStore
277
277
  form_data = resource.try(:form_data?)
278
278
 
279
279
  if form_data
280
- form_request_parse(resource)
280
+ form_request_parse(resource)
281
281
  else
282
282
  MultiJson.dump(to_hash(resource))
283
283
  end
@@ -285,7 +285,7 @@ class Stormpath::DataStore
285
285
 
286
286
  def form_request_parse(resource)
287
287
  data = ""
288
-
288
+
289
289
  property_names = resource.get_dirty_property_names
290
290
  property_names.each do |name|
291
291
  if name != "formData"
@@ -305,29 +305,37 @@ class Stormpath::DataStore
305
305
  property = resource.get_property name, ignore_camelcasing: ignore_camelcasing
306
306
 
307
307
  # Special use cases are with Custom Data, Provider and ProviderData, their hashes should not be simplified
308
- if property.kind_of?(Hash) and !resource_nested_submittable(resource, name)
308
+ if property.kind_of?(Hash) and !resource_nested_submittable(resource, name) and name != "items"
309
309
  property = to_simple_reference name, property
310
310
  end
311
311
 
312
+ if name == "items" and resource_is_saml_mapping_rules? resource
313
+ property = property.map { |item| item.transform_keys { |key| key.to_s.camelize(:lower).to_sym } }
314
+ end
315
+
312
316
  properties.store name, property
313
317
  end
314
318
  end
315
319
  end
316
320
 
317
321
  def to_simple_reference(property_name, hash)
318
- assert_true hash.has_key?(HREF_PROP_NAME), "Nested resource '#{property_name}' must have an 'href' property."
322
+ assert_true hash.key?(HREF_PROP_NAME), "Nested resource '#{property_name}' must have an 'href' property."
319
323
 
320
324
  href = hash[HREF_PROP_NAME]
321
325
 
322
- {HREF_PROP_NAME => href}
326
+ { HREF_PROP_NAME => href }
323
327
  end
324
328
 
325
329
  def resource_nested_submittable resource, name
326
- ['provider', 'providerData'].include?(name) or resource_is_custom_data(resource, name)
330
+ ['provider', 'providerData', 'accountStore'].include?(name) or resource_is_custom_data(resource, name)
327
331
  end
328
332
 
329
333
  def resource_is_custom_data resource, name
330
334
  resource.is_a? Stormpath::Resource::CustomData or name == 'customData'
331
335
  end
332
336
 
337
+ def resource_is_saml_mapping_rules? resource
338
+ resource.is_a? Stormpath::Provider::SamlMappingRules
339
+ end
340
+
333
341
  end