devise_ichain_authenticatable 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/Gemfile +3 -0
- data/MIT-LICENSE +20 -0
- data/README.md +5 -0
- data/app/controllers/devise/ichain_registrations_controller.rb +56 -0
- data/app/controllers/devise/ichain_sessions_controller.rb +52 -0
- data/app/views/devise/ichain_registrations/edit.html.erb +12 -0
- data/app/views/devise/ichain_sessions/new.html.erb +19 -0
- data/app/views/devise/ichain_sessions/new_test.html.erb +12 -0
- data/devise_ichain_authenticatable.gemspec +22 -0
- data/lib/devise/ichain_failure_app.rb +29 -0
- data/lib/devise_ichain_authenticatable.rb +31 -0
- data/lib/devise_ichain_authenticatable/models.rb +24 -0
- data/lib/devise_ichain_authenticatable/rails.rb +4 -0
- data/lib/devise_ichain_authenticatable/routes.rb +21 -0
- data/lib/devise_ichain_authenticatable/strategy.rb +37 -0
- data/lib/devise_ichain_authenticatable/version.rb +3 -0
- data/rails/init.rb +1 -0
- metadata +75 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MWNiYWNhMjM4N2ZmMmZlMzViMGYxOTExMjdjN2VhYTlhNjNlYWZmOQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YjkzOTBjMjg0NGRhNzA0NjgzMmRkNzQwNGYyODJkOWFiZTNkNDJiZg==
|
7
|
+
!binary "U0hBNTEy":
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MDc5ZTJlMjViODRmOWFmZTMyODVlMGE0NzAzN2UxMjAyYzgxOGZmZjQ2NmMw
|
10
|
+
MGI1MDdlMmNiZTkwZWQ4NDY3OTI2Mzg3MDAxNjViODQ0NWI2MGY5YmZmYTFh
|
11
|
+
Y2E1YmZhYzQ3YzIwOTg5YzY2OTYwNTc5Mzc5YmJiZDMxOWYzOGI=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
M2Y2YzE2NTJhMzg4MmJlMzMzYmQwODgxZDk5ZjFhZmY1NzBlNmQ3ZGE4MzA4
|
14
|
+
OTQ3NGFiNDM3OWZkNTJmNDEzMDA4YzI3NWFmNTQ4MWZjMWI3ZWFlNjhhYjAw
|
15
|
+
ODE0ODZmZjU4MWQ2ZGJmZDgzNGEyZWVhNDg1MzUzODcyMDI2YWY=
|
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Ancor González
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
class Devise::IchainRegistrationsController < DeviseController
|
2
|
+
prepend_before_filter :require_no_authentication, :only => [ :new ]
|
3
|
+
prepend_before_filter :authenticate_scope!, :only => [:edit, :update]
|
4
|
+
|
5
|
+
def new
|
6
|
+
redirect_url = base_url + after_sign_up_path_for(resource_name)
|
7
|
+
if ::Devise.ichain_test_mode
|
8
|
+
set_flash_message :notice, :in_test_mode
|
9
|
+
redirect_to redirect_url
|
10
|
+
else
|
11
|
+
sign_up_url = ::Devise.ichain_base_url + "/ICSLogin"
|
12
|
+
sign_up_url += "?" + {:url => redirect_url}.to_query
|
13
|
+
redirect_to sign_up_url
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# GET /resource/edit
|
18
|
+
def edit
|
19
|
+
render :edit
|
20
|
+
end
|
21
|
+
|
22
|
+
# PUT /resource
|
23
|
+
# We need to use a copy of the resource because we don't want to change
|
24
|
+
# the current user in place.
|
25
|
+
def update
|
26
|
+
#self.resource = resource_class.to_adapter.get!(send(:"current_#{resource_name}").to_key)
|
27
|
+
resource.update_attributes(resource_params)
|
28
|
+
respond_with resource, :location => after_update_path_for(resource)
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# The default url to be used after updating a resource. You need to overwrite
|
34
|
+
# this method in your own RegistrationsController.
|
35
|
+
def after_update_path_for(resource)
|
36
|
+
signed_in_root_path(resource)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Authenticates the current scope and gets the current resource from the session.
|
40
|
+
def authenticate_scope!
|
41
|
+
send(:"authenticate_#{resource_name}!", :force => true)
|
42
|
+
self.resource = send(:"current_#{resource_name}")
|
43
|
+
end
|
44
|
+
|
45
|
+
# The path used after sign up. You need to overwrite this method
|
46
|
+
# in your own RegistrationsController.
|
47
|
+
def after_sign_up_path_for(resource)
|
48
|
+
after_sign_in_path_for(resource)
|
49
|
+
end
|
50
|
+
|
51
|
+
def base_url
|
52
|
+
url = "#{request.protocol}#{request.host}"
|
53
|
+
url += ":#{request.port}" unless request.port.blank?
|
54
|
+
url
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Devise::IchainSessionsController < DeviseController
|
2
|
+
prepend_before_filter :require_no_authentication, :only => [ :new, :test ]
|
3
|
+
|
4
|
+
# GET /resource/sign_in
|
5
|
+
def new
|
6
|
+
return new_test if ::Devise.ichain_test_mode
|
7
|
+
self.resource = build_resource(nil, :unsafe => true)
|
8
|
+
@back_url = base_url + after_sign_in_path_for(resource_name)
|
9
|
+
@login_url = (::Devise.ichain_base_url || "") + "/ICSLogin/auth-up"
|
10
|
+
@context = ::Devise.ichain_context
|
11
|
+
@proxypath = ::Devise.ichain_proxypath
|
12
|
+
respond_with resource
|
13
|
+
end
|
14
|
+
|
15
|
+
# DELETE /resource/sign_out
|
16
|
+
def destroy
|
17
|
+
redirect_url = base_url + after_sign_out_path_for(resource_name)
|
18
|
+
if ::Devise.ichain_test_mode
|
19
|
+
session.delete :ichain_test_username
|
20
|
+
session.delete :ichain_test_attributes
|
21
|
+
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
|
22
|
+
set_flash_message :notice, :signed_out if signed_out && is_navigational_format?
|
23
|
+
redirect_to redirect_url
|
24
|
+
else
|
25
|
+
logout_url = ::Devise.ichain_base_url + "/ICHAINLogout"
|
26
|
+
logout_url += "?" + {:url => redirect_url}.to_query
|
27
|
+
redirect_to logout_url
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def test
|
32
|
+
redirect_to(:new) unless Devise::ichain_test_mode
|
33
|
+
session[:ichain_test_username] = params[:username]
|
34
|
+
session[:ichain_test_attributes] = params[:attributes]
|
35
|
+
redirect_to after_sign_in_path_for(resource_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def new_test
|
41
|
+
self.resource = build_resource(nil, :unsafe => true)
|
42
|
+
@fields = (::Devise.ichain_attribute_headers.keys rescue [])
|
43
|
+
@login_url = test_ichain_session_path(resource_name)
|
44
|
+
render :new_test
|
45
|
+
end
|
46
|
+
|
47
|
+
def base_url
|
48
|
+
url = "#{request.protocol}#{request.host}"
|
49
|
+
url += ":#{request.port}" unless request.port.blank?
|
50
|
+
url
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<h2>Edit <%= resource_name.to_s.humanize %></h2>
|
2
|
+
|
3
|
+
<%= form_for(resource, :as => resource_name, :url => ichain_registration_path(resource_name), :html => { :method => :put }) do |f| %>
|
4
|
+
<%= devise_error_messages! %>
|
5
|
+
|
6
|
+
<div><%= f.label :email %><br />
|
7
|
+
<%= f.email_field :email, :autofocus => true %></div>
|
8
|
+
|
9
|
+
<div><%= f.submit "Update" %></div>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<%= link_to "Back", :back %>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<h2>Sign in</h2>
|
2
|
+
|
3
|
+
<%= form_tag(@login_url, :method => :post, :enctype => 'application/x-www-form-urlencoded') do %>
|
4
|
+
<%= hidden_field_tag :url, @back_url %>
|
5
|
+
<%= hidden_field_tag :context, @context %>
|
6
|
+
<%= hidden_field_tag :proxypath, @proxypath %>
|
7
|
+
|
8
|
+
<div><%= label_tag :username, "Username" %><br />
|
9
|
+
<%= text_field_tag :username, "", :autofocus => true %></div>
|
10
|
+
|
11
|
+
<div><%= label_tag :Password, "Password" %><br />
|
12
|
+
<%= text_field_tag :password, "" %></div>
|
13
|
+
|
14
|
+
<div><%= submit_tag "Sign in" %></div>
|
15
|
+
<% end %>
|
16
|
+
|
17
|
+
<%- if devise_mapping.ichain_registerable? %>
|
18
|
+
<%= link_to "Sign up", new_ichain_registration_path(resource_name) %><br />
|
19
|
+
<% end -%>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<h2>Sign in (TEST MODE)</h2>
|
2
|
+
|
3
|
+
<%= form_tag(@login_url, :method => :post, :enctype => 'application/x-www-form-urlencoded') do %>
|
4
|
+
<div><%= label_tag :username, "Username" %><br />
|
5
|
+
<%= text_field_tag :username, "" %></div>
|
6
|
+
<%- @fields.each do |field| %>
|
7
|
+
<div><%= label_tag "attributes[#{field}]", field.to_s.humanize %><br />
|
8
|
+
<%= text_field_tag "attributes[#{field}]", "" %></div>
|
9
|
+
<%- end -%>
|
10
|
+
|
11
|
+
<div><%= submit_tag "Sign in" %></div>
|
12
|
+
<% end %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "devise_ichain_authenticatable/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'devise_ichain_authenticatable'
|
7
|
+
s.version = DeviseIchainAuthenticatable::VERSION.dup
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.summary = 'Devise extension to allow authentication via iChain'
|
10
|
+
s.email = 'ancor@suse.de'
|
11
|
+
s.homepage = 'https://github.com/openSUSE/devise_ichain_authenticatable'
|
12
|
+
s.description = s.summary
|
13
|
+
s.authors = ['Ancor González Sosa']
|
14
|
+
s.license = 'MIT'
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency(%q<devise>, ['>= 2.2'])
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "devise/failure_app"
|
2
|
+
|
3
|
+
class ::Devise::IchainFailureApp < ::Devise::FailureApp
|
4
|
+
protected
|
5
|
+
|
6
|
+
# TODO: find a better way to do that (or even open an issue for Devise)
|
7
|
+
# Add ichain_session as a fallback to session
|
8
|
+
def scope_path
|
9
|
+
opts = {}
|
10
|
+
route = :"new_#{scope}_session_path"
|
11
|
+
alt_route = :"new_#{scope}_ichain_session_path"
|
12
|
+
opts[:format] = request_format unless skip_format?
|
13
|
+
|
14
|
+
config = Rails.application.config
|
15
|
+
opts[:script_name] = (config.relative_url_root if config.respond_to?(:relative_url_root))
|
16
|
+
|
17
|
+
context = send(Devise.available_router_name)
|
18
|
+
|
19
|
+
if context.respond_to?(route)
|
20
|
+
context.send(route, opts)
|
21
|
+
elsif context.respond_to?(alt_route)
|
22
|
+
context.send(alt_route, opts)
|
23
|
+
elsif respond_to?(:root_path)
|
24
|
+
root_path(opts)
|
25
|
+
else
|
26
|
+
"/"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'devise'
|
2
|
+
require 'devise_ichain_authenticatable/routes'
|
3
|
+
require 'devise_ichain_authenticatable/strategy'
|
4
|
+
require 'devise_ichain_authenticatable/models'
|
5
|
+
require 'devise_ichain_authenticatable/rails'
|
6
|
+
|
7
|
+
module Devise
|
8
|
+
|
9
|
+
autoload :IchainFailureApp, 'devise/ichain_failure_app'
|
10
|
+
|
11
|
+
# Configuration params
|
12
|
+
@@ichain_test_mode = false
|
13
|
+
@@ichain_base_url = nil
|
14
|
+
@@ichain_context = "default"
|
15
|
+
@@ichain_proxypath = "reverse"
|
16
|
+
@@ichain_username_header = "HTTP_X_USERNAME"
|
17
|
+
@@ichain_attribute_headers = {:email => "HTTP_X_EMAIL"}
|
18
|
+
|
19
|
+
mattr_accessor :ichain_test_mode, :ichain_base_url, :ichain_context,
|
20
|
+
:ichain_proxypath, :ichain_username_header, :ichain_attribute_headers
|
21
|
+
end
|
22
|
+
|
23
|
+
Devise.add_module :ichain_authenticatable,
|
24
|
+
:strategy => true,
|
25
|
+
:controller => :ichain_sessions,
|
26
|
+
:route => {:ichain_session => [nil, :new, :test, :destroy]}
|
27
|
+
|
28
|
+
Devise.add_module :ichain_registerable,
|
29
|
+
:strategy => false,
|
30
|
+
:controller => :ichain_registrations,
|
31
|
+
:route => {:ichain_registration => [nil, :new, :edit]}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Devise
|
2
|
+
module Models
|
3
|
+
module IchainAuthenticatable
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
def signed_in_by_ichain!
|
12
|
+
@signed_in_by_ichain = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def signed_in_by_ichain?
|
16
|
+
@signed_in_by_ichain == true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Empty, but Devise seems to require a model per module
|
21
|
+
module IchainRegisterable
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
ActionDispatch::Routing::Mapper.class_eval do
|
2
|
+
protected
|
3
|
+
|
4
|
+
def devise_ichain_session(mapping, controllers)
|
5
|
+
resource :ichain_session, :only => [], :controller => controllers[:ichain_sessions], :path => "" do
|
6
|
+
get :new, :path => mapping.path_names[:ichain_sign_in], :as => "new"
|
7
|
+
post :test, :as => "test"
|
8
|
+
match :destroy, :path => mapping.path_names[:ichain_sign_out], :as => "destroy", :via => mapping.sign_out_via
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def devise_ichain_registration(mapping, controllers)
|
13
|
+
options = {
|
14
|
+
:only => [:new, :edit, :update],
|
15
|
+
:path => mapping.path_names[:ichain_registration],
|
16
|
+
:path_names => {:new => mapping.path_names[:ichain_sign_up] },
|
17
|
+
:controller => controllers[:ichain_registrations]
|
18
|
+
}
|
19
|
+
resource :ichain_registration, options
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'devise/strategies/authenticatable'
|
2
|
+
|
3
|
+
class Devise::Strategies::IchainAuthenticatable < Devise::Strategies::Authenticatable
|
4
|
+
|
5
|
+
def store?
|
6
|
+
false
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
::Devise.ichain_test_mode || !request.env[::Devise.ichain_username_header].blank?
|
11
|
+
end
|
12
|
+
|
13
|
+
def authenticate!
|
14
|
+
if ::Devise.ichain_test_mode
|
15
|
+
unless session[:ichain_test_username].blank?
|
16
|
+
proxy_user = session[:ichain_test_username]
|
17
|
+
attributes = session[:ichain_test_attributes] || {}
|
18
|
+
end
|
19
|
+
else
|
20
|
+
proxy_user = request.env[::Devise.ichain_username_header]
|
21
|
+
attributes = {}
|
22
|
+
::Devise.ichain_attribute_headers.each do |k,v|
|
23
|
+
attributes[k.to_sym] = request.env[v]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
if proxy_user
|
27
|
+
resource = mapping.to.for_ichain_username(proxy_user, attributes)
|
28
|
+
return fail! unless resource
|
29
|
+
resource.signed_in_by_ichain!
|
30
|
+
success!(resource)
|
31
|
+
else
|
32
|
+
fail!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
Warden::Strategies.add :ichain_authenticatable, Devise::Strategies::IchainAuthenticatable
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "devise_ichain_authenticatable"
|
metadata
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: devise_ichain_authenticatable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ancor González Sosa
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-07-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: devise
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ! '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.2'
|
27
|
+
description: Devise extension to allow authentication via iChain
|
28
|
+
email: ancor@suse.de
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- Gemfile
|
34
|
+
- MIT-LICENSE
|
35
|
+
- README.md
|
36
|
+
- app/controllers/devise/ichain_registrations_controller.rb
|
37
|
+
- app/controllers/devise/ichain_sessions_controller.rb
|
38
|
+
- app/views/devise/ichain_registrations/edit.html.erb
|
39
|
+
- app/views/devise/ichain_sessions/new.html.erb
|
40
|
+
- app/views/devise/ichain_sessions/new_test.html.erb
|
41
|
+
- devise_ichain_authenticatable.gemspec
|
42
|
+
- lib/devise/ichain_failure_app.rb
|
43
|
+
- lib/devise_ichain_authenticatable.rb
|
44
|
+
- lib/devise_ichain_authenticatable/models.rb
|
45
|
+
- lib/devise_ichain_authenticatable/rails.rb
|
46
|
+
- lib/devise_ichain_authenticatable/routes.rb
|
47
|
+
- lib/devise_ichain_authenticatable/strategy.rb
|
48
|
+
- lib/devise_ichain_authenticatable/version.rb
|
49
|
+
- rails/init.rb
|
50
|
+
homepage: https://github.com/openSUSE/devise_ichain_authenticatable
|
51
|
+
licenses:
|
52
|
+
- MIT
|
53
|
+
metadata: {}
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 2.0.3
|
71
|
+
signing_key:
|
72
|
+
specification_version: 4
|
73
|
+
summary: Devise extension to allow authentication via iChain
|
74
|
+
test_files: []
|
75
|
+
has_rdoc:
|