dot_net_services 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|