dot_net_services 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +24 -0
- data/README +52 -0
- data/lib/dot_net_services/authentication.rb +168 -0
- data/lib/dot_net_services/error.rb +4 -0
- data/lib/dot_net_services/message_buffer.rb +269 -0
- data/lib/dot_net_services/session.rb +282 -0
- data/lib/dot_net_services.rb +143 -0
- data/lib/net/http/create_mb.rb +14 -0
- data/lib/net/http/retrieve.rb +14 -0
- data/lib/net/http/subscribe.rb +14 -0
- data/lib/net/http/unsubscribe.rb +14 -0
- data/spec/integration/TestService/Service/AnonymousResourceService.cs +9 -0
- data/spec/integration/TestService/Service/App.config +32 -0
- data/spec/integration/TestService/Service/PlainTextService.cs +37 -0
- data/spec/integration/TestService/Service/Program.cs +49 -0
- data/spec/integration/TestService/Service/Properties/AssemblyInfo.cs +33 -0
- data/spec/integration/TestService/Service/ResourceContract.cs +17 -0
- data/spec/integration/TestService/Service/ResourceService.cs +58 -0
- data/spec/integration/TestService/Service/Service.csproj +71 -0
- data/spec/integration/TestService/TestService.sln +33 -0
- data/spec/integration/end_to_end_spec.rb +84 -0
- data/spec/integration/vmb_spec.rb +30 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/unit/dot_net_services/authentication_spec.rb +289 -0
- data/spec/unit/dot_net_services/message_buffer_spec.rb +161 -0
- data/spec/unit/dot_net_services/session_spec.rb +247 -0
- metadata +87 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
namespace DotNetServicesRuby
|
2
|
+
{
|
3
|
+
using System;
|
4
|
+
using System.Collections.Generic;
|
5
|
+
using System.ServiceModel;
|
6
|
+
using System.ServiceModel.Channels;
|
7
|
+
|
8
|
+
[ServiceBehavior(Name = "SyndicationService", Namespace = "http://samples.microsoft.com/ServiceModel/Relay/")]
|
9
|
+
class ResourceService : ResourceContract
|
10
|
+
{
|
11
|
+
class HimomBodyWriter : BodyWriter
|
12
|
+
{
|
13
|
+
string query;
|
14
|
+
string verb;
|
15
|
+
string body;
|
16
|
+
|
17
|
+
public HimomBodyWriter(string theQuery, string theVerb, string theBody) : base(true)
|
18
|
+
{
|
19
|
+
query = theQuery;
|
20
|
+
verb = theVerb;
|
21
|
+
body = theBody;
|
22
|
+
}
|
23
|
+
|
24
|
+
protected override void OnWriteBodyContents(System.Xml.XmlDictionaryWriter writer)
|
25
|
+
{
|
26
|
+
writer.WriteStartElement("body");
|
27
|
+
writer.WriteString("\n");
|
28
|
+
writer.WriteString("Hi, mom\n");
|
29
|
+
writer.WriteString("verb: " + verb + "\n");
|
30
|
+
writer.WriteString("query: " + query + "\n");
|
31
|
+
writer.WriteString("body: " + body + "\n");
|
32
|
+
writer.WriteEndElement();
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
public Message Process(Message message)
|
37
|
+
{
|
38
|
+
var httpRequest = (HttpRequestMessageProperty)(message.Properties["httpRequest"]);
|
39
|
+
|
40
|
+
var query = httpRequest.QueryString;
|
41
|
+
var verb = httpRequest.Method;
|
42
|
+
var body = "";
|
43
|
+
|
44
|
+
Message response = Message.CreateMessage(message.Version, "GETRESPONSE", new HimomBodyWriter(query, verb, body));
|
45
|
+
|
46
|
+
HttpResponseMessageProperty responseProperty = new HttpResponseMessageProperty();
|
47
|
+
responseProperty.Headers.Add("Content-Type", "application/xml; charset=utf-8");
|
48
|
+
response.Properties.Add(HttpResponseMessageProperty.Name, responseProperty);
|
49
|
+
return response;
|
50
|
+
}
|
51
|
+
|
52
|
+
public Message ProcessPost(Message message)
|
53
|
+
{
|
54
|
+
throw new NotImplementedException();
|
55
|
+
}
|
56
|
+
|
57
|
+
}
|
58
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="3.5">
|
2
|
+
<PropertyGroup>
|
3
|
+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
4
|
+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
5
|
+
<ProductVersion>9.0.21022</ProductVersion>
|
6
|
+
<SchemaVersion>2.0</SchemaVersion>
|
7
|
+
<ProjectGuid>{27CF1BE6-0280-46F8-BB19-BC7147E02B6A}</ProjectGuid>
|
8
|
+
<OutputType>Exe</OutputType>
|
9
|
+
<AppDesignerFolder>Properties</AppDesignerFolder>
|
10
|
+
<RootNamespace>System.ServiceBus.Samples</RootNamespace>
|
11
|
+
<AssemblyName>Service</AssemblyName>
|
12
|
+
<FileUpgradeFlags>
|
13
|
+
</FileUpgradeFlags>
|
14
|
+
<OldToolsVersion>2.0</OldToolsVersion>
|
15
|
+
<UpgradeBackupLocation>
|
16
|
+
</UpgradeBackupLocation>
|
17
|
+
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
18
|
+
</PropertyGroup>
|
19
|
+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
20
|
+
<DebugSymbols>true</DebugSymbols>
|
21
|
+
<DebugType>full</DebugType>
|
22
|
+
<Optimize>false</Optimize>
|
23
|
+
<OutputPath>bin\Debug\</OutputPath>
|
24
|
+
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
25
|
+
<ErrorReport>prompt</ErrorReport>
|
26
|
+
<WarningLevel>4</WarningLevel>
|
27
|
+
</PropertyGroup>
|
28
|
+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
29
|
+
<DebugType>pdbonly</DebugType>
|
30
|
+
<Optimize>true</Optimize>
|
31
|
+
<OutputPath>bin\Release\</OutputPath>
|
32
|
+
<DefineConstants>TRACE</DefineConstants>
|
33
|
+
<ErrorReport>prompt</ErrorReport>
|
34
|
+
<WarningLevel>4</WarningLevel>
|
35
|
+
</PropertyGroup>
|
36
|
+
<ItemGroup>
|
37
|
+
<Reference Include="Microsoft.ServiceBus, Version=0.12.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
|
38
|
+
<Reference Include="System" />
|
39
|
+
<Reference Include="System.Core">
|
40
|
+
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
41
|
+
</Reference>
|
42
|
+
<Reference Include="System.Data" />
|
43
|
+
<Reference Include="System.Runtime.Serialization, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
|
44
|
+
<SpecificVersion>False</SpecificVersion>
|
45
|
+
</Reference>
|
46
|
+
<Reference Include="System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL" />
|
47
|
+
<Reference Include="System.ServiceModel.Web">
|
48
|
+
<RequiredTargetFramework>3.5</RequiredTargetFramework>
|
49
|
+
</Reference>
|
50
|
+
<Reference Include="System.Xml" />
|
51
|
+
</ItemGroup>
|
52
|
+
<ItemGroup>
|
53
|
+
<Compile Include="AnonymousResourceService.cs" />
|
54
|
+
<Compile Include="PlainTextService.cs" />
|
55
|
+
<Compile Include="Program.cs" />
|
56
|
+
<Compile Include="Properties\AssemblyInfo.cs" />
|
57
|
+
<Compile Include="ResourceContract.cs" />
|
58
|
+
<Compile Include="ResourceService.cs" />
|
59
|
+
</ItemGroup>
|
60
|
+
<ItemGroup>
|
61
|
+
<None Include="App.config" />
|
62
|
+
</ItemGroup>
|
63
|
+
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
64
|
+
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
65
|
+
Other similar extension points exist, see Microsoft.Common.targets.
|
66
|
+
<Target Name="BeforeBuild">
|
67
|
+
</Target>
|
68
|
+
<Target Name="AfterBuild">
|
69
|
+
</Target>
|
70
|
+
-->
|
71
|
+
</Project>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
Microsoft Visual Studio Solution File, Format Version 10.00
|
3
|
+
# Visual Studio 2008
|
4
|
+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F20C1D91-6F22-4047-85D9-423AB58D1C5C}"
|
5
|
+
ProjectSection(SolutionItems) = preProject
|
6
|
+
Readme.htm = Readme.htm
|
7
|
+
EndProjectSection
|
8
|
+
ProjectSection(WebsiteProperties) = preProject
|
9
|
+
Debug.AspNetCompiler.Debug = "True"
|
10
|
+
Release.AspNetCompiler.Debug = "False"
|
11
|
+
EndProjectSection
|
12
|
+
EndProject
|
13
|
+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Service", "Service\Service.csproj", "{27CF1BE6-0280-46F8-BB19-BC7147E02B6A}"
|
14
|
+
ProjectSection(WebsiteProperties) = preProject
|
15
|
+
Debug.AspNetCompiler.Debug = "True"
|
16
|
+
Release.AspNetCompiler.Debug = "False"
|
17
|
+
EndProjectSection
|
18
|
+
EndProject
|
19
|
+
Global
|
20
|
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
21
|
+
Debug|Any CPU = Debug|Any CPU
|
22
|
+
Release|Any CPU = Release|Any CPU
|
23
|
+
EndGlobalSection
|
24
|
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
25
|
+
{27CF1BE6-0280-46F8-BB19-BC7147E02B6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
26
|
+
{27CF1BE6-0280-46F8-BB19-BC7147E02B6A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
27
|
+
{27CF1BE6-0280-46F8-BB19-BC7147E02B6A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
28
|
+
{27CF1BE6-0280-46F8-BB19-BC7147E02B6A}.Release|Any CPU.Build.0 = Release|Any CPU
|
29
|
+
EndGlobalSection
|
30
|
+
GlobalSection(SolutionProperties) = preSolution
|
31
|
+
HideSolutionNode = FALSE
|
32
|
+
EndGlobalSection
|
33
|
+
EndGlobal
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
describe "end to end usage scenarios" do
|
4
|
+
|
5
|
+
it "should be able to perform a simple GET from the endpoint" do
|
6
|
+
result = DotNetServices::Session.open(anonymous_test_endpoint) { |s| s.get }
|
7
|
+
result.class.to_s.should == "Net::HTTPOK"
|
8
|
+
result.content_type.should == 'application/xml'
|
9
|
+
result.body.should =~ /^Hi, mom$/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should pass query string through a GET correctly" do
|
13
|
+
result = DotNetServices::Session.open(anonymous_test_endpoint) { |s| s.get :foo => 'bar' }
|
14
|
+
result.class.to_s.should == "Net::HTTPOK"
|
15
|
+
result.content_type.should == 'application/xml'
|
16
|
+
result.body.should =~ /^query: foo=bar/
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should pass query string with URL escaped characters through a GET correctly" do
|
20
|
+
result = DotNetServices::Session.open(anonymous_test_endpoint) { |s| s.get :foo => 'b+a~r+' }
|
21
|
+
result.class.to_s.should == "Net::HTTPOK"
|
22
|
+
result.content_type.should == 'application/xml'
|
23
|
+
result.body.should =~ /^query: foo=b\+a~r/
|
24
|
+
end
|
25
|
+
|
26
|
+
# This scenario doesn't work, known bug in CTP 12
|
27
|
+
# it "should pass a form data through a POST" do
|
28
|
+
# result = DotNetServices::Session.open(test_endpoint) { |s| s.post :foo => 'bar' }
|
29
|
+
# result.class.to_s.should == "Net::HTTPOK"
|
30
|
+
# result.content_type.should == 'application/xml'
|
31
|
+
# result.body.should =~ /^verb: POST$/
|
32
|
+
# result.body.should =~ /^body: foo=bar$/
|
33
|
+
# end
|
34
|
+
|
35
|
+
it "should obtain a security token" do
|
36
|
+
DotNetServices::Session.open(test_endpoint, :username => 'alexeyv', :password => '12345678') do
|
37
|
+
|session|
|
38
|
+
session.authenticator.token.should_not be_nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should be able to do an authenticated GET" do
|
43
|
+
result = DotNetServices::Session.open(test_endpoint, :username => 'alexeyv', :password => '12345678') { |s| s.get }
|
44
|
+
result.class.to_s.should == "Net::HTTPOK"
|
45
|
+
result.body.should =~ /^Hi, mom$/
|
46
|
+
# TODO: once we know how, make sure that service has some idea of who is talking to it
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should pass a form data through an authenticated POST" do
|
50
|
+
result = DotNetServices::Session.open(test_endpoint, :username => 'alexeyv', :password => '12345678') { |s| s.post :foo => 'bar' }
|
51
|
+
result.class.to_s.should == "Net::HTTPOK"
|
52
|
+
result.content_type.should == 'application/xml'
|
53
|
+
result.body.should =~ /^verb: POST$/
|
54
|
+
result.body.should =~ /^body: foo=bar$/
|
55
|
+
end
|
56
|
+
|
57
|
+
it "plain text GET" do
|
58
|
+
result = DotNetServices::Session.open(plain_text_test_endpoint, :username => 'alexeyv', :password => '12345678') { |s| s.get :foo => 'bar' }
|
59
|
+
result.class.to_s.should == "Net::HTTPOK"
|
60
|
+
result.body.should == "hi mom"
|
61
|
+
end
|
62
|
+
|
63
|
+
it "plain text POST" do
|
64
|
+
result = DotNetServices::Session.open(plain_text_test_endpoint, :username => 'alexeyv', :password => '12345678') { |s| s.post :foo => 'bar' }
|
65
|
+
result.class.to_s.should == "Net::HTTPOK"
|
66
|
+
result.content_type.should == 'text/plain'
|
67
|
+
result.body.should =~ /^verb: POST$/
|
68
|
+
result.body.should =~ /^body: foo=bar$/
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should detect an invalid user/password situation and blow up correctly"
|
72
|
+
|
73
|
+
|
74
|
+
# it "should not be slow as a turtle with laryngitis walking through molasses in January" do
|
75
|
+
# start = Time.now
|
76
|
+
# 10.times { DotNetServices::Session.open(anonymous_test_endpoint) { |s| s.get :foo => 'bar' } }
|
77
|
+
# duration = Time.now - start
|
78
|
+
#
|
79
|
+
# if duration > 10
|
80
|
+
# raise "10 simple get requests took #{duration} seconds to perform. This is too long."
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../spec_helper"
|
2
|
+
|
3
|
+
describe "end to end usage scenarios" do
|
4
|
+
|
5
|
+
it "should do end-to-end VMB interaction with itself" do
|
6
|
+
authenticator = DotNetServices::Authentication.setup(:username => 'RubyInterop', :password => '12345678')
|
7
|
+
authenticator.authenticate
|
8
|
+
authenticator.token.should_not be_nil
|
9
|
+
|
10
|
+
create_request = Net::HTTP::CreateMB.new("/services/RubyInterop/MessageBuffer")
|
11
|
+
|
12
|
+
authenticator.enhance(create_request)
|
13
|
+
|
14
|
+
puts
|
15
|
+
p create_request
|
16
|
+
create_request.each { |k, v| puts "#{k}: #{v}"}
|
17
|
+
puts
|
18
|
+
|
19
|
+
result = Net::HTTP.start(DotNetServices.relay_host) { |http| http.request(create_request) }
|
20
|
+
|
21
|
+
p result
|
22
|
+
puts
|
23
|
+
result.each { |k, v| puts "#{k}: #{v}"}
|
24
|
+
puts
|
25
|
+
puts result.body
|
26
|
+
puts
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')))
|
3
|
+
|
4
|
+
require 'spec'
|
5
|
+
require 'dot_net_services'
|
6
|
+
|
7
|
+
Spec::Runner.configure do |config|
|
8
|
+
config.before(:each) do
|
9
|
+
DotNetServices::Authentication.clear_cache!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_endpoint
|
14
|
+
DotNetServices.root_url + "/BillBoard/TestService/"
|
15
|
+
end
|
16
|
+
|
17
|
+
def anonymous_test_endpoint
|
18
|
+
DotNetServices.root_url + "/BillBoard/AnonymousTestService/"
|
19
|
+
end
|
20
|
+
|
21
|
+
def plain_text_test_endpoint
|
22
|
+
DotNetServices.root_url + "/BillBoard/PlainTextTestService/"
|
23
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
require "#{File.dirname(__FILE__)}/../../spec_helper"
|
2
|
+
|
3
|
+
module DotNetServices
|
4
|
+
|
5
|
+
describe Authentication::UsernamePassword do
|
6
|
+
|
7
|
+
describe "acting as a hash key" do
|
8
|
+
before :each do
|
9
|
+
@auth = Authentication::UsernamePassword.new('frodo', 'passw0rd')
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should not be equal to an instance of another class" do
|
13
|
+
different_class = stub('not the same class', :username => 'frodo', :password => 'passw0rd')
|
14
|
+
different_class.should_receive(:is_a?).with(Authentication::UsernamePassword).and_return(false)
|
15
|
+
@auth.should_not == different_class
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should not be equal to an instance with different username or password" do
|
19
|
+
@auth.should_not == Authentication::UsernamePassword.new('gandalf', 'passw0rd')
|
20
|
+
@auth.should_not == Authentication::UsernamePassword.new('frodo', 'different passw0rd')
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should equal to another instance with same username and password" do
|
24
|
+
@auth.should == @auth
|
25
|
+
@auth.should == Authentication::UsernamePassword.new('frodo', 'passw0rd')
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should adequately identify an instance in a hash" do
|
29
|
+
hash = {}
|
30
|
+
hash[@auth] = :found
|
31
|
+
hash[@auth].should == :found
|
32
|
+
|
33
|
+
equivalent_auth = Authentication::UsernamePassword.new('frodo', 'passw0rd')
|
34
|
+
hash[equivalent_auth].should == :found
|
35
|
+
|
36
|
+
different_auth = Authentication::UsernamePassword.new('frodo', 'different passw0rd')
|
37
|
+
hash[different_auth].should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "authenticate()" do
|
43
|
+
|
44
|
+
it "should obtain a token from DotNetServices STS" do
|
45
|
+
http = mock_http
|
46
|
+
response = mock('response')
|
47
|
+
Time.stub!(:now).and_return(Time.at(0))
|
48
|
+
|
49
|
+
http.should_receive(:get).with("/issuetoken.aspx?u=frodo&p=password").and_return(response)
|
50
|
+
response.should_receive(:is_a?).with(Net::HTTPOK).and_return(true)
|
51
|
+
response.stub!(:body).and_return('bWyFxQgby0gHyPWZ2LcIXnHgA1J5kALK4iM+QA==')
|
52
|
+
|
53
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password')
|
54
|
+
auth.authenticate
|
55
|
+
auth.token.value.should == 'bWyFxQgby0gHyPWZ2LcIXnHgA1J5kALK4iM+QA=='
|
56
|
+
auth.token.expiry.should == Time.at(24 * 60 * 60)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should rewrite exception messages to avoid accidentally exposing username/password" do
|
60
|
+
http = mock_http
|
61
|
+
http.should_receive(:get).with("/issuetoken.aspx?u=frodo&p=password").and_raise("p=password")
|
62
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password')
|
63
|
+
|
64
|
+
begin
|
65
|
+
auth.authenticate
|
66
|
+
rescue => e
|
67
|
+
e.should be_instance_of(AuthenticationError)
|
68
|
+
e.message.should_not =~ /password/
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should detect when the HTTP response code is wrong and blow up correctly" do
|
73
|
+
http = mock_http
|
74
|
+
not_found = stub("HTTPNotFound", :class => Net::HTTPNotFound)
|
75
|
+
not_found.should_receive(:is_a?).with(Net::HTTPOK).and_return(false)
|
76
|
+
|
77
|
+
http.should_receive(:get).with("/issuetoken.aspx?u=frodo&p=password").and_return(not_found)
|
78
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password')
|
79
|
+
|
80
|
+
lambda { auth.authenticate }.should raise_error(AuthenticationError,
|
81
|
+
"Failed to obtain a security token from the identity service. HTTP response was Net::HTTPNotFound")
|
82
|
+
end
|
83
|
+
|
84
|
+
# TODO Identity service response for this situation is not correct now. Implement test when they fix it
|
85
|
+
it "should detect an invalid user/password situation and blow up correctly"
|
86
|
+
|
87
|
+
# TODO asked MS what future-proof assertions can be made to see if it's a token
|
88
|
+
it "should detect when response is not a token and blow up correctly"
|
89
|
+
# do
|
90
|
+
# http = mock_http
|
91
|
+
# response = mock('response')
|
92
|
+
# http.should_receive(:get).with("/issuetoken.aspx?u=frodo&p=password").and_return(response)
|
93
|
+
# response.should_receive(:is_a?).with(Net::HTTPOK).and_return(true)
|
94
|
+
# response.stub!(:body).and_return('NOT A TOKEN')
|
95
|
+
#
|
96
|
+
# auth = Authentication::UsernamePassword.new('frodo', 'password')
|
97
|
+
# proc { auth.authenticate }.should raise_error(
|
98
|
+
# AuthenticationError, "Response from identity service is not a valid token. " +
|
99
|
+
# # TODO remove the line below when we are able to detect this situation explicitly
|
100
|
+
# "This can indicate username/password mismatch.")
|
101
|
+
# end
|
102
|
+
|
103
|
+
it "should not acquire a new token if a token is already defined" do
|
104
|
+
token = Authentication::Token.new("nh8hUpTSAeMW0RVkMRRaZtxJqfM==")
|
105
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password', token)
|
106
|
+
Net::HTTP.should_not_receive(:new)
|
107
|
+
auth.authenticate
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should acquire new token if token is expired" do
|
111
|
+
expired_token = Authentication::Token.new('expired/token==', Time.now - 1)
|
112
|
+
new_token = Authentication::Token.new('new/token==', Time.now)
|
113
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password', expired_token)
|
114
|
+
|
115
|
+
auth.should_receive(:acquire_token).and_return(new_token)
|
116
|
+
request = {}
|
117
|
+
auth.enhance(request)
|
118
|
+
auth.token.should equal(new_token)
|
119
|
+
request['X-MS-Identity-Token'].should == 'new/token=='
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should acquire new token if token is not set" do
|
123
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password')
|
124
|
+
auth.token.should be_nil
|
125
|
+
new_token = Authentication::Token.new('new/token==', Time.now)
|
126
|
+
|
127
|
+
auth.should_receive(:acquire_token).and_return(new_token)
|
128
|
+
|
129
|
+
request = {}
|
130
|
+
auth.enhance(request)
|
131
|
+
auth.token.should equal(new_token)
|
132
|
+
request['X-MS-Identity-Token'].should == 'new/token=='
|
133
|
+
end
|
134
|
+
|
135
|
+
def mock_http
|
136
|
+
http = mock('http')
|
137
|
+
Net::HTTP.should_receive(:new).with(DotNetServices.identity_host, 443).and_return(http)
|
138
|
+
http.should_receive(:use_ssl=).with(true)
|
139
|
+
http.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE)
|
140
|
+
http
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "enhance(request)" do
|
146
|
+
|
147
|
+
it "should put a token into X-MS-Identity-Token header" do
|
148
|
+
Time.stub!(:now).and_return(Time.at(0))
|
149
|
+
request = {}
|
150
|
+
token = Authentication::Token.new("a/token==", Time.at(1))
|
151
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password', token)
|
152
|
+
auth.enhance(request)
|
153
|
+
request['X-MS-Identity-Token'].should == "a/token=="
|
154
|
+
end
|
155
|
+
|
156
|
+
it "should acquire the token if necessary" do
|
157
|
+
token = Authentication::Token.new("a/token==", Time.at(1))
|
158
|
+
auth = Authentication::UsernamePassword.new('frodo', 'password')
|
159
|
+
auth.should_receive(:acquire_token).and_return(token)
|
160
|
+
request = {}
|
161
|
+
auth.enhance(request)
|
162
|
+
request['X-MS-Identity-Token'].should == "a/token=="
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
|
169
|
+
describe Authentication::Anonymous do
|
170
|
+
describe "acting as a hash key" do
|
171
|
+
it "should equal to any instance of the same class" do
|
172
|
+
auth = Authentication::Anonymous.new
|
173
|
+
auth.should == auth
|
174
|
+
auth.should == Authentication::Anonymous.new
|
175
|
+
auth.should_not == Object.new
|
176
|
+
end
|
177
|
+
|
178
|
+
it "should have a constant hash" do
|
179
|
+
auth = Authentication::Anonymous.new
|
180
|
+
auth.hash.should == -1
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe "setup(auth_data)" do
|
186
|
+
it "should construct a user/password authentication handler given user and password" do
|
187
|
+
authenticator = Authentication.setup(:username => 'me', :password => 'himom')
|
188
|
+
authenticator.should be_instance_of(Authentication::UsernamePassword)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should construct an anonymous authentication handler given nothing" do
|
192
|
+
authenticator = Authentication.setup(nil)
|
193
|
+
authenticator.should be_instance_of(Authentication::Anonymous)
|
194
|
+
end
|
195
|
+
|
196
|
+
it "should construct an anonymous authentication handler given an empty hash" do
|
197
|
+
authenticator = Authentication.setup({})
|
198
|
+
authenticator.should be_instance_of(Authentication::Anonymous)
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should construct a certificate authentication handler given certificate" do
|
202
|
+
authenticator = Authentication.setup(:certificate => 'foo')
|
203
|
+
authenticator.should be_instance_of(Authentication::Certificate)
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should treat auth_data as auth handler if it's not a hash" do
|
207
|
+
authenticator = Authentication.setup(:foo)
|
208
|
+
authenticator.should == :foo
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should blow up if username is given but password isn't, or vice versa" do
|
212
|
+
proc {
|
213
|
+
Authentication.setup(:username => 'foo')
|
214
|
+
}.should raise_error(ArgumentError, "Auth data specifies username, but no password.")
|
215
|
+
|
216
|
+
proc {
|
217
|
+
authenticator = Authentication.setup(:password => 'foo')
|
218
|
+
}.should raise_error(ArgumentError, "Auth data specifies password, but no username.")
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should blow up if both username/password and certificate are in auth_data hash" do
|
222
|
+
proc {
|
223
|
+
authenticator = Authentication.setup(:username => 'foo', :password => 'bar', :certificate => 'baz')
|
224
|
+
}.should raise_error(ArgumentError, "Cannot determine authentication type from auth data.")
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should blow up if given unknown options in auth data" do
|
228
|
+
proc {
|
229
|
+
authenticator = Authentication.setup(:username => 'foo', :password => 'bar', :something => true, :something_else => true)
|
230
|
+
}.should raise_error(ArgumentError, /^Auth data contains unknown options:/)
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should cache authenticators" do
|
234
|
+
Authentication.instance_variable_get(:@cache).size.should == 0
|
235
|
+
|
236
|
+
anon_auth = Authentication.setup(nil)
|
237
|
+
Authentication.instance_variable_get(:@cache).length.should == 1
|
238
|
+
|
239
|
+
another_anon_auth = Authentication.setup(nil)
|
240
|
+
Authentication.instance_variable_get(:@cache).length.should == 1
|
241
|
+
another_anon_auth.should equal(anon_auth)
|
242
|
+
|
243
|
+
user_auth = Authentication.setup(:username => 'frodo', :password => 'passw0rd')
|
244
|
+
Authentication.instance_variable_get(:@cache).length.should == 2
|
245
|
+
another_user_auth = Authentication.setup(:username => 'frodo', :password => 'passw0rd')
|
246
|
+
Authentication.instance_variable_get(:@cache).length.should == 2
|
247
|
+
another_user_auth.should equal(user_auth)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should reacquire expired tokens" do
|
251
|
+
old_token = Authentication::Token.new('old/token==', Time.at(1))
|
252
|
+
new_token = Authentication::Token.new('new/token==', Time.at(4))
|
253
|
+
|
254
|
+
Time.stub!(:now).and_return(Time.at(0))
|
255
|
+
old_auth = Authentication.setup(:username => 'frodo', :password => 'passw0rd')
|
256
|
+
|
257
|
+
# old_auth will have to reacquire the token because the one it had initially will be expired
|
258
|
+
old_auth.should_receive(:acquire_token).twice.and_return(old_token, new_token)
|
259
|
+
|
260
|
+
old_auth.authenticate
|
261
|
+
old_auth.token.should equal(old_token)
|
262
|
+
|
263
|
+
Time.stub!(:now).and_return(Time.at(2))
|
264
|
+
|
265
|
+
new_auth = Authentication.setup(:username => 'frodo', :password => 'passw0rd')
|
266
|
+
new_auth.authenticate
|
267
|
+
|
268
|
+
new_auth.should equal(old_auth)
|
269
|
+
new_auth.token.should equal(new_token)
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
describe Authentication::Token do
|
275
|
+
it "should check that value is a well-formed token" do
|
276
|
+
lambda { Authentication::Token.new("foo") }.
|
277
|
+
should raise_error(AuthenticationError,
|
278
|
+
/^Response from access control service doesn't seem to contain a valid authentication token/)
|
279
|
+
end
|
280
|
+
|
281
|
+
it "should ignore the rest of the body, if there is anything after the token (bug workaround)" do
|
282
|
+
token = Authentication::Token.new('bWyFxQgby0gHyPWZ2LcIXnHgA1J5kALK4iM+QA==<html>....</html>')
|
283
|
+
token.value.should == 'bWyFxQgby0gHyPWZ2LcIXnHgA1J5kALK4iM+QA=='
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|