cassette 1.0.0

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.
@@ -0,0 +1,15 @@
1
+ module Cassette
2
+ class Version
3
+ MAJOR = "1"
4
+ MINOR = "0"
5
+
6
+ def self.build_number
7
+ ENV["BUILD_NUMBER"] || 0
8
+ end
9
+
10
+ def self.version
11
+ [MAJOR, MINOR, build_number].join(".")
12
+ end
13
+ end
14
+ end
15
+
data/lib/cassette.rb ADDED
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+
3
+ require "cassette/errors"
4
+ require "cassette/cache"
5
+ require "cassette/client/cache"
6
+ require "cassette/client"
7
+ require "cassette/authentication"
8
+ require "cassette/authentication/authorities"
9
+ require "cassette/authentication/user"
10
+ require "cassette/authentication/cache"
11
+ require "cassette/authentication/filter"
12
+
13
+ require "faraday"
14
+ require "logger"
15
+
16
+ module Cassette
17
+ extend self
18
+
19
+ DEFAULT_TIMEOUT = 10
20
+
21
+ def logger
22
+ @@logger ||= begin
23
+ if defined?(Rails) && Rails.logger
24
+ Rails.logger
25
+ else
26
+ Logger.new("/dev/null")
27
+ end
28
+ end
29
+ end
30
+
31
+ def logger=(logger)
32
+ @@logger = logger
33
+ end
34
+
35
+ def config
36
+ @@config
37
+ end
38
+
39
+ def config=(config)
40
+ @@config = config
41
+ end
42
+
43
+ def new_request(uri, timeout)
44
+ Faraday.new(url: uri, ssl: { verify: false, version: "TLSv1" }) do |builder|
45
+ builder.adapter :httpclient
46
+ builder.options.timeout = timeout
47
+ end
48
+ end
49
+
50
+ def get(uri, payload, timeout = DEFAULT_TIMEOUT)
51
+ perform(:get, uri, payload, timeout) do |req|
52
+ req.params = payload
53
+ logger.debug "Request: #{req.inspect}"
54
+ end
55
+ end
56
+
57
+ def post(uri, payload, timeout = DEFAULT_TIMEOUT)
58
+ perform(:post, uri, payload, timeout) do |req|
59
+ req.body = payload
60
+ logger.debug "Request: #{req.inspect}"
61
+ end
62
+ end
63
+
64
+ protected
65
+
66
+ def perform(op, uri, payload, timeout = DEFAULT_TIMEOUT, &block)
67
+ request = new_request(uri, timeout)
68
+ res = request.send(op, &block)
69
+
70
+ res.tap do |response|
71
+ logger.debug "Got response: #{response.body.inspect} (#{response.status}), #{response.headers.inspect}"
72
+ Cassette::Errors.raise_by_code(response.status) unless response.success?
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cassette::Authentication::Authorities do
4
+ subject do
5
+ Cassette::Authentication::Authorities
6
+ end
7
+
8
+ describe "#has_role?" do
9
+ let(:input) { "[#{Cassette.config.base_authority}, SAPI, #{Cassette.config.base_authority}_CREATE-USER]" }
10
+ let(:authorities) { subject.parse(input) }
11
+
12
+ it "adds the application prefix to roles" do
13
+ expect(authorities.has_role?("CREATE-USER")).to eql(true)
14
+ end
15
+
16
+ it "ignores role case" do
17
+ expect(authorities.has_role?("create-user")).to eql(true)
18
+ end
19
+
20
+ it "replaces underscores with dashes" do
21
+ expect(authorities.has_role?("create_user")).to eql(true)
22
+ end
23
+ end
24
+
25
+ context "with a defined base authority" do
26
+ let(:base_authority) { "SOMEAPI" }
27
+
28
+ it "stores the base authority" do
29
+ input = "CUSTOMERAPI"
30
+ expect(subject.parse(input, base_authority).base).to eql(base_authority)
31
+ end
32
+
33
+ describe "#has_role?" do
34
+ let(:input) { "[#{Cassette.config.base_authority}_TEST2, SOMEAPI_TEST]" }
35
+
36
+ it "returns true for a role that is using the base authority" do
37
+ expect(subject.parse(input, base_authority)).to have_role(:test)
38
+ end
39
+
40
+ it "returns false for a role that is not using the base authority" do
41
+ expect(subject.parse(input, base_authority)).not_to have_role(:test2)
42
+ end
43
+ end
44
+ end
45
+
46
+ context "CAS authorities parsing" do
47
+ it "handles single authority" do
48
+ input = "CUSTOMERAPI"
49
+ expect(subject.parse(input).authorities).to eq(%w(CUSTOMERAPI))
50
+ end
51
+
52
+ it "handles multiple authorities with surrounding []" do
53
+ input = "[CUSTOMERAPI, SAPI]"
54
+ expect(subject.parse(input).authorities).to eq(%w(CUSTOMERAPI SAPI))
55
+ end
56
+
57
+ it "ignores whitespace in multiple authorities" do
58
+ input = "[CUSTOMERAPI,SAPI]"
59
+ expect(subject.parse(input).authorities).to eq(%w(CUSTOMERAPI SAPI))
60
+ end
61
+
62
+ it "returns an empty array when input is nil" do
63
+ expect(subject.parse(nil).authorities).to eq([])
64
+ end
65
+ end
66
+
67
+ context "with authentication disabled" do
68
+ before { ENV["NOAUTH"] = "true" }
69
+ after { ENV.delete("NOAUTH") }
70
+ subject { Cassette::Authentication::Authorities.new("[]") }
71
+
72
+ it "#has_role? returns true for every role" do
73
+ expect(subject.authorities).to be_empty
74
+ expect(subject.has_role?(:can_manage)).to eql(true)
75
+ end
76
+
77
+ it "#has_raw_role? returns true for every role" do
78
+ expect(subject.authorities).to be_empty
79
+ expect(subject.has_raw_role?("SAPI_CUSTOMER-CREATOR")).to eql(true)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,8 @@
1
+
2
+ # encoding: utf-8
3
+
4
+ require 'spec_helper'
5
+
6
+ describe Cassette::Authentication::Cache do
7
+ pending
8
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+ require "active_support/core_ext/hash/indifferent_access"
5
+
6
+ describe Cassette::Authentication::Filter do
7
+ before do
8
+ allow(Cassette::Authentication).to receive(:validate_ticket)
9
+ end
10
+
11
+ class ControllerMock
12
+ attr_accessor :params, :request, :current_user
13
+ def self.before_filter(*); end
14
+ include Cassette::Authentication::Filter
15
+
16
+ def initialize(params = {}, headers = {})
17
+ self.params = params.with_indifferent_access
18
+ self.request = OpenStruct.new(headers: headers.with_indifferent_access)
19
+ end
20
+ end
21
+
22
+ shared_context "with NOAUTH" do
23
+ before do
24
+ ENV["NOAUTH"] = "yes"
25
+ end
26
+
27
+ after do
28
+ ENV.delete("NOAUTH")
29
+ end
30
+ end
31
+
32
+ describe "#validate_raw_role!" do
33
+ let(:controller) { ControllerMock.new }
34
+ let(:current_user) { instance_double(Cassette::Authentication::User) }
35
+
36
+ before do
37
+ allow(controller).to receive(:current_user).and_return(current_user)
38
+ end
39
+
40
+ it_behaves_like "with NOAUTH" do
41
+ it "never checks the role" do
42
+ expect(current_user).not_to receive(:has_raw_role?)
43
+ controller.validate_raw_role!(:something)
44
+ end
45
+
46
+ it "does not raise error" do
47
+ expect { controller.validate_raw_role!(:something) }.not_to raise_error
48
+ end
49
+ end
50
+
51
+ it "forwards to current_user" do
52
+ role = instance_double(String)
53
+
54
+ expect(current_user).to receive(:has_raw_role?).with(role).and_return(true)
55
+ controller.validate_raw_role!(role)
56
+ end
57
+
58
+ it "raises a Cassette::Errors::Forbidden when current_user does not have the role" do
59
+ role = instance_double(String)
60
+
61
+ expect(current_user).to receive(:has_raw_role?).with(role).and_return(false)
62
+ expect { controller.validate_raw_role!(role) }.to raise_error(Cassette::Errors::Forbidden)
63
+ end
64
+ end
65
+
66
+ describe "#validate_role!" do
67
+ let(:controller) { ControllerMock.new }
68
+ let(:current_user) { instance_double(Cassette::Authentication::User) }
69
+
70
+ before do
71
+ allow(controller).to receive(:current_user).and_return(current_user)
72
+ end
73
+
74
+ it_behaves_like "with NOAUTH" do
75
+ it "never checks the role" do
76
+ expect(current_user).not_to receive(:has_role?)
77
+ controller.validate_role!(:something)
78
+ end
79
+
80
+ it "does not raise error" do
81
+ expect { controller.validate_role!(:something) }.not_to raise_error
82
+ end
83
+ end
84
+
85
+ it "forwards to current_user" do
86
+ role = instance_double(String)
87
+
88
+ expect(current_user).to receive(:has_role?).with(role).and_return(true)
89
+ controller.validate_role!(role)
90
+ end
91
+
92
+ it "raises a Cassette::Errors::Forbidden when current_user does not have the role" do
93
+ role = instance_double(String)
94
+
95
+ expect(current_user).to receive(:has_role?).with(role).and_return(false)
96
+ expect { controller.validate_role!(role) }.to raise_error(Cassette::Errors::Forbidden)
97
+ end
98
+ end
99
+
100
+ describe "#validate_authentication_ticket" do
101
+ it_behaves_like "with NOAUTH" do
102
+ context "and no ticket" do
103
+ let(:controller) { ControllerMock.new }
104
+
105
+ it "should not validate tickets" do
106
+ controller.validate_authentication_ticket
107
+ expect(Cassette::Authentication).not_to have_received(:validate_ticket)
108
+ end
109
+
110
+ it "should set current_user" do
111
+ controller.validate_authentication_ticket
112
+ expect(controller.current_user).to be_present
113
+ end
114
+ end
115
+
116
+ context "and a ticket header" do
117
+ let(:controller) do
118
+ ControllerMock.new({}, "Service-Ticket" => "le ticket")
119
+ end
120
+
121
+ it "should validate tickets" do
122
+ controller.validate_authentication_ticket
123
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with("le ticket", Cassette.config.service)
124
+ end
125
+ end
126
+
127
+ context "and a ticket param" do
128
+ let(:controller) do
129
+ ControllerMock.new(ticket: "le ticket")
130
+ end
131
+
132
+ it "should validate tickets" do
133
+ controller.validate_authentication_ticket
134
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with("le ticket", Cassette.config.service)
135
+ end
136
+ end
137
+ end
138
+
139
+ context "with a ticket in the query string *AND* headers" do
140
+ let(:controller) do
141
+ ControllerMock.new({"ticket" => "le other ticket"}, "Service-Ticket" => "le ticket")
142
+ end
143
+
144
+ it "should send only the header ticket to validation" do
145
+ controller.validate_authentication_ticket
146
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with("le ticket", Cassette.config.service)
147
+ end
148
+ end
149
+
150
+ context "with a ticket in the query string" do
151
+ let(:controller) do
152
+ ControllerMock.new("ticket" => "le ticket")
153
+ end
154
+
155
+ it "should send the ticket to validation" do
156
+ controller.validate_authentication_ticket
157
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with("le ticket", Cassette.config.service)
158
+ end
159
+ end
160
+
161
+ context "with a ticket in the Service-Ticket header" do
162
+ let(:controller) do
163
+ ControllerMock.new({}, "Service-Ticket" => "le ticket")
164
+ end
165
+
166
+ it "should send the ticket to validation" do
167
+ controller.validate_authentication_ticket
168
+ expect(Cassette::Authentication).to have_received(:validate_ticket).with("le ticket", Cassette.config.service)
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Cassette::Authentication::User do
4
+ let(:base_authority) do
5
+ Cassette.config.base_authority
6
+ end
7
+
8
+ describe "#initialize" do
9
+ context "without a config" do
10
+ it "forwards authorities parsing" do
11
+ expect(Cassette::Authentication::Authorities).to receive(:new).with("[CUSTOMERAPI, SAPI]", nil)
12
+ Cassette::Authentication::User.new(login: "john.doe", name: "John Doe", authorities: "[CUSTOMERAPI, SAPI]")
13
+ end
14
+ end
15
+
16
+ context "with a config" do
17
+ it "forwards authorities parsing passing along the base authority" do
18
+ config = object_double(Cassette.config)
19
+
20
+ expect(config).to receive(:base_authority).and_return("TESTAPI")
21
+ expect(Cassette::Authentication::Authorities).to receive(:new).with("[CUSTOMERAPI, SAPI]", "TESTAPI")
22
+
23
+ Cassette::Authentication::User.new(login: "john.doe", name: "John Doe", authorities: "[CUSTOMERAPI, SAPI]", config: config)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe "#has_role?" do
29
+ let (:user) do
30
+ Cassette::Authentication::User.new(login: "john.doe", name: "John Doe",
31
+ authorities: "[#{base_authority}, SAPI, #{base_authority}_CREATE-USER]")
32
+ end
33
+
34
+ it "adds the application prefix to roles" do
35
+ expect(user.has_role?("CREATE-USER")).to eql(true)
36
+ end
37
+
38
+ it "ignores role case" do
39
+ expect(user.has_role?("create-user")).to eql(true)
40
+ end
41
+
42
+ it "replaces underscores with dashes" do
43
+ expect(user.has_role?("create_user")).to eql(true)
44
+ end
45
+ end
46
+
47
+ context "user types" do
48
+ context "#employee?" do
49
+ it "returns true when user is an employee" do
50
+ expect(Cassette::Authentication::User.new(type: "employee")).to be_employee
51
+ expect(Cassette::Authentication::User.new(type: "Employee")).to be_employee
52
+ expect(Cassette::Authentication::User.new(type: :employee)).to be_employee
53
+ expect(Cassette::Authentication::User.new(type: "customer")).not_to be_employee
54
+ expect(Cassette::Authentication::User.new(type: nil)).not_to be_employee
55
+ expect(Cassette::Authentication::User.new(type: "")).not_to be_employee
56
+ end
57
+ end
58
+
59
+ context "#customer?" do
60
+ it "returns true when the user is a customer" do
61
+ expect(Cassette::Authentication::User.new(type: "customer")).to be_customer
62
+ expect(Cassette::Authentication::User.new(type: "Customer")).to be_customer
63
+ expect(Cassette::Authentication::User.new(type: :customer)).to be_customer
64
+ expect(Cassette::Authentication::User.new(type: "employee")).not_to be_customer
65
+ expect(Cassette::Authentication::User.new(type: nil)).not_to be_customer
66
+ expect(Cassette::Authentication::User.new(type: "")).not_to be_customer
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,84 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Cassette::Authentication do
6
+ let(:cache) { instance_double(Cassette::Authentication::Cache) }
7
+ let(:http) { class_double(Cassette) }
8
+
9
+ subject do
10
+ Cassette::Authentication.new(cache: cache, http_client: http)
11
+ end
12
+
13
+ describe "#ticket_user" do
14
+ context "when cached" do
15
+ it "returns the cached value when cached" do
16
+ cached = double('cached')
17
+
18
+ expect(cache).to receive(:fetch_authentication) do |ticket, &block|
19
+ expect(ticket).to eql("ticket")
20
+ expect(block).to be_present
21
+ cached
22
+ end
23
+
24
+ expect(subject.ticket_user("ticket")).to eql(cached)
25
+ end
26
+ end
27
+
28
+ context "when not cached" do
29
+ before do
30
+ expect(cache).to receive(:fetch_authentication) do |ticket, &block|
31
+ block.call
32
+ end
33
+ end
34
+
35
+ it "raises a Forbidden exception on any exceptions" do
36
+ allow(http).to receive(:post).with(anything, anything).and_raise(Cassette::Errors::BadRequest)
37
+ expect { subject.ticket_user("ticket") }.to raise_error(Cassette::Errors::Forbidden)
38
+ end
39
+
40
+ context "with a failed CAS response" do
41
+ before do
42
+ allow(http).to receive(:post).with(anything, anything)
43
+ .and_return(OpenStruct.new(body: fixture("cas/fail.xml")))
44
+ end
45
+
46
+ it "returns nil" do
47
+ expect(subject.ticket_user("ticket")).to be_nil
48
+ end
49
+ end
50
+
51
+ context "with a successful CAS response" do
52
+ before do
53
+ allow(http).to receive(:post).with(anything, anything)
54
+ .and_return(OpenStruct.new(body: fixture("cas/success.xml")))
55
+ end
56
+
57
+ it "returns an User" do
58
+ expect(subject.ticket_user("ticket")).to be_instance_of(Cassette::Authentication::User)
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ describe "#validate_ticket" do
65
+ it "raises a authorization required error when no ticket is provided" do
66
+ expect { subject.validate_ticket(nil) }.to raise_error(Cassette::Errors::AuthorizationRequired)
67
+ end
68
+
69
+ it "raises a authorization required error when ticket is blank" do
70
+ expect { subject.validate_ticket("") }.to raise_error(Cassette::Errors::AuthorizationRequired)
71
+ end
72
+
73
+ it "raises a forbidden error when the associated user is not found" do
74
+ expect(subject).to receive(:ticket_user).with("ticket", Cassette.config.service).and_return(nil)
75
+ expect { subject.validate_ticket("ticket") }.to raise_error(Cassette::Errors::Forbidden)
76
+ end
77
+
78
+ it "returns the associated user" do
79
+ user = double('User')
80
+ expect(subject).to receive(:ticket_user).with("ticket", Cassette.config.service).and_return(user)
81
+ expect(subject.validate_ticket("ticket")).to eql(user)
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Cassette::Cache do
6
+ subject do
7
+ c = Class.new
8
+ c.send(:include, Cassette::Cache)
9
+ c.new
10
+ end
11
+
12
+ describe "backend" do
13
+ before { subject.backend = nil }
14
+ after { subject.backend = nil }
15
+
16
+ it "sets the backend" do
17
+ backend = double('Backend')
18
+ subject.backend = backend
19
+ expect(subject.backend).to eql(backend)
20
+ end
21
+
22
+ it "defaults to rails backend" do
23
+ rails = double("Rails")
24
+ allow(rails).to receive(:cache).and_return(rails)
25
+ stub_const("Rails", rails)
26
+
27
+ expect(subject.backend).to eql(rails)
28
+ end
29
+ end
30
+
31
+ it "invalidates the cache after the configured number of uses" do
32
+ generator = double('Generator')
33
+ expect(generator).to receive(:generate).twice
34
+
35
+ 6.times do
36
+ subject.fetch("Generator", max_uses: 5) { generator.generate }
37
+ end
38
+ end
39
+ end
40
+
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Cassette::Client::Cache do
6
+ pending
7
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Cassette::Errors do
6
+ describe Cassette::Errors::Base do
7
+ describe "#code" do
8
+ it "returns the HTTP status code accordlingly" do
9
+ expect(Cassette::Errors::Forbidden.new.code).to eql(403)
10
+ expect(Cassette::Errors::NotFound.new.code).to eql(404)
11
+ expect(Cassette::Errors::InternalServerError.new.code).to eql(500)
12
+ end
13
+ end
14
+ end
15
+
16
+ describe ".raise_by_code" do
17
+ it "raises the correct exception for the status code" do
18
+ expect { Cassette::Errors.raise_by_code(404) }.to raise_error(Cassette::Errors::NotFound)
19
+ expect { Cassette::Errors.raise_by_code(403) }.to raise_error(Cassette::Errors::Forbidden)
20
+ expect { Cassette::Errors.raise_by_code(412) }.to raise_error(Cassette::Errors::PreconditionFailed)
21
+ expect { Cassette::Errors.raise_by_code(500) }.to raise_error(Cassette::Errors::InternalServerError)
22
+ end
23
+
24
+ it "raises internal server error for unmapped errors" do
25
+ expect { Cassette::Errors.raise_by_code(406) }.to raise_error(Cassette::Errors::InternalServerError)
26
+ expect { Cassette::Errors.raise_by_code(200) }.to raise_error(Cassette::Errors::InternalServerError)
27
+ end
28
+ end
29
+ end
data/spec/cas_spec.rb ADDED
@@ -0,0 +1,78 @@
1
+ require "spec_helper"
2
+
3
+ describe Cassette do
4
+ let(:uri) { "http://example.org/" }
5
+ let(:response) do
6
+ Faraday.new do |builder|
7
+ builder.adapter :test do |stub|
8
+ stub.post(uri, 'data') do |env|
9
+ headers = env.request_headers
10
+ [200, {}, "{ok: true}"]
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ let(:failed_response) do
17
+ Faraday.new do |builder|
18
+ builder.adapter :test do |stub|
19
+ stub.post(uri, 'data') do |env|
20
+ headers = env.request_headers
21
+ [500, {}, "{ok: false}"]
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ describe ".new_request" do
28
+ it "returns an instance" do
29
+ # damn coverage
30
+ expect(Cassette.new_request(uri, 5)).to be_instance_of(Faraday::Connection)
31
+ end
32
+ end
33
+
34
+ describe ".post" do
35
+ it "forwards requests" do
36
+ allow(Cassette).to receive(:new_request).with(uri, 5).and_return(response)
37
+ Cassette.post(uri, "data", 5)
38
+ end
39
+
40
+ it "raises an exception when failed" do
41
+ allow(Cassette).to receive(:new_request).with(uri, 5).and_return(failed_response)
42
+ expect { Cassette.post(uri, "data", 5) }.to raise_error(Cassette::Errors::InternalServerError)
43
+ end
44
+ end
45
+
46
+ def keeping_logger(&block)
47
+ original_logger = Cassette.logger
48
+ block.call
49
+ Cassette.logger = original_logger
50
+ end
51
+
52
+ describe ".logger" do
53
+ it "returns a default instance" do
54
+ expect(Cassette.logger).not_to be_nil
55
+ expect(Cassette.logger.kind_of?(Logger)).to eql(true)
56
+ end
57
+
58
+ it "returns rails logger when Rails is available" do
59
+ keeping_logger do
60
+ Cassette.logger = nil
61
+ rails = double("Rails")
62
+ expect(rails).to receive(:logger).and_return(rails).at_least(:once)
63
+ stub_const("Rails", rails)
64
+ expect(Cassette.logger).to eql(rails)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe ".logger=" do
70
+ let(:logger) { Logger.new(STDOUT) }
71
+ it "defines the logger instance" do
72
+ keeping_logger do
73
+ Cassette.logger = logger
74
+ expect(Cassette.logger).to eq(logger)
75
+ end
76
+ end
77
+ end
78
+ end
data/spec/config.yml ADDED
@@ -0,0 +1,5 @@
1
+ username: "test"
2
+ password: "inicial1234"
3
+ service: "test-api.devintegration.locaweb.com.br"
4
+ base: "https://systems-login.systemintegration.locaweb.com.br"
5
+ base_authority: "CASTEST"
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0"?>
2
+ <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3
+ <cas:authenticationFailure code='INVALID_TICKET'>
4
+ ticket &#039;whatever&#039; não reconhecido
5
+ </cas:authenticationFailure>
6
+ </cas:serviceResponse>