Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding SSO CILogon #872

Merged
merged 100 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
100 commits
Select commit Hold shift + click to select a range
cc8c5f2
Adding a openid connect gem
Jun 28, 2024
09d9e31
Omniauth Callback changes, this is not finalized will be changing
Jun 28, 2024
a0d3e5e
adding cilogon to the devise settings and user changes, this is not f…
200455939-yashu Jun 28, 2024
efcecc7
adding sign-in with the cilogon option
200455939-yashu Jun 28, 2024
3d2bf95
adding the openid devise settings
200455939-yashu Jun 28, 2024
89b45e9
adding cilogon callback to the route
200455939-yashu Jun 28, 2024
c6d46bd
adding the session changes to the cilogon, not finalized
200455939-yashu Jun 28, 2024
3634c70
session changes to support cilogon
200455939-yashu Jun 28, 2024
12d8393
changes not required will be cleaned
200455939-yashu Jun 28, 2024
a8a2409
Merge remote-tracking branch 'origin/integration' into yashu-sso-cilogon
200455939-yashu Jun 28, 2024
a54a0a4
Temporary changes to test the credentials
200455939-yashu Jul 9, 2024
160a9fc
This file contains multiple trial and error methods on callback needs…
200455939-yashu Jul 19, 2024
00071d5
since we will be having the eppn value with cilogon instead of the uid
200455939-yashu Jul 19, 2024
dd715a6
Devise openid_connect trial and errors
200455939-yashu Jul 19, 2024
86fabb4
merge after resolving gem lock file conflicts by accepting the incomi…
200455939-yashu Jul 19, 2024
afb6e58
This is working code so far! still need lot of clean-up on this
200455939-yashu Jul 26, 2024
adf7840
Merge remote-tracking branch 'origin/integration' into yashu-sso-cilogon
200455939-yashu Jul 26, 2024
8f501fd
The debugging of the sessions. This major Needs cleanup
200455939-yashu Jul 30, 2024
39eb671
Altering the MAX cookies size for the browser
200455939-yashu Aug 1, 2024
db6d65c
To enable the recaptcha in localhost we use 127 instead of localhost
200455939-yashu Aug 8, 2024
7f8f71b
This the working code of the SSO sing-in testing connect to ORCID
200455939-yashu Aug 14, 2024
0c21df3
Merge remote-tracking branch 'origin/integration' into yashu-sso-cilogon
200455939-yashu Aug 14, 2024
d645bb6
WIP Updates to sso
lagoan Aug 14, 2024
ae66ef0
code clean up and added test cases
200455939-yashu Aug 19, 2024
072b68d
Add omniauth tests and fix identifier_scheme bug
lagoan Aug 22, 2024
c51cbb4
Cleanup of integration tests
lagoan Aug 22, 2024
fdd9adf
Testcases in progress changes
200455939-yashu Aug 22, 2024
af061db
Merging the branches
200455939-yashu Aug 22, 2024
8ce73fc
Merging the branches
200455939-yashu Aug 22, 2024
04223f9
WIP of testcases
200455939-yashu Aug 23, 2024
ab73c4f
adding the keys secrets file
200455939-yashu Aug 26, 2024
e8ef17f
reupdating the return on empty email
200455939-yashu Aug 26, 2024
b79800b
Removing commented byebugs
200455939-yashu Aug 26, 2024
0fc1876
Test cases with flash one is clearing checking the other one
200455939-yashu Aug 27, 2024
a9d7a2f
fixes with button name and uri updates
200455939-yashu Aug 27, 2024
19661b1
syntax correction
200455939-yashu Aug 27, 2024
5a1db67
updates with URI url and testcase link with
200455939-yashu Aug 27, 2024
f76758b
Add link account with CILogon
lagoan Aug 27, 2024
277ae25
Add missing changes
lagoan Aug 28, 2024
cb05397
Merge branch 'yashu-sso-cilogon' into yashu-sso-link-accounts
lagoan Aug 28, 2024
9148ffe
Fix failing tests due to incorrect mocking
lagoan Aug 28, 2024
54edd81
Add extended scope for SSO
lagoan Aug 28, 2024
4ddfff5
Update devise initializer
lagoan Aug 28, 2024
9600dfd
Add another devise fix
lagoan Aug 28, 2024
0cdc68f
Pass tests for openid_connect_sso_test
lagoan Aug 28, 2024
4bcae55
Return maximum name for identifier scheme
lagoan Aug 28, 2024
eab41b0
Testcases for the Ominiauth controller openid connect
200455939-yashu Aug 29, 2024
050b7db
Code cleanup - specs controllers omniauth
200455939-yashu Aug 29, 2024
2242154
Robust name handling when creating user & CC
lagoan Aug 29, 2024
d392ceb
More code cleanup
lagoan Aug 29, 2024
b929fbc
Code cleanup
lagoan Aug 29, 2024
40ebe29
Code cleanup
lagoan Aug 29, 2024
6823d7f
Code cleanup
lagoan Aug 29, 2024
87bef5a
Add missing line in new plans view
lagoan Aug 30, 2024
22fde67
Issue fix for the multiple accounts
200455939-yashu Aug 30, 2024
8da1e3a
Clean up configuration
lagoan Aug 30, 2024
e95c10c
Resolving conflicts after the yashu-sso-link-accounts
200455939-yashu Aug 30, 2024
377f62c
Merge remote-tracking branch 'origin/yashu-sso-2user-accounts-issue-f…
200455939-yashu Aug 30, 2024
45e54e3
Resolving the conflicts after merge
200455939-yashu Aug 30, 2024
a07d923
Code cleanup
lagoan Aug 30, 2024
8ab358a
Add mising translation piece
lagoan Aug 30, 2024
b31c2f9
Code cleanup
lagoan Aug 30, 2024
84c2ef7
adding test cases for the linked successfylly and 2 users condition
200455939-yashu Aug 30, 2024
58d4ee8
Translation related changes
200455939-yashu Aug 30, 2024
9678eda
Merge remote-tracking branch 'origin/yashu-sso-link-accounts' into ya…
200455939-yashu Aug 30, 2024
a3b5ab6
Spelling correction
200455939-yashu Aug 30, 2024
7f70edf
Removing the byebug and the updates related to translations.
200455939-yashu Sep 3, 2024
4da5fdd
Add CHANGELOG entry
lagoan Sep 3, 2024
0aece6b
Adding the changelog
200455939-yashu Sep 3, 2024
3649013
commit after Resolving the conflicts in Changelog
200455939-yashu Sep 3, 2024
7eb53a1
Review Changes
200455939-yashu Sep 4, 2024
28d4e33
Fix tests
lagoan Sep 5, 2024
87b2fa3
Merge branch 'yashu-sso-link-accounts' into yashu-controller-spec
200455939-yashu Sep 5, 2024
1fb6100
Merge branch 'integration' into yashu-sso-link-accounts
lagoan Sep 5, 2024
8f7ef2b
About and help page updates of SSO
200455939-yashu Sep 5, 2024
c21e69d
Merge branch 'yashu-sso-link-accounts' into yashu-controller-spec
200455939-yashu Sep 6, 2024
991dabd
PR update on changelog
200455939-yashu Sep 6, 2024
277f0a6
Update CHANGELOG entry
lagoan Sep 6, 2024
d423f09
Merge branch 'yashu-sso-link-accounts' from repo
lagoan Sep 6, 2024
afcef7a
Merge branch 'yashu-sso-link-accounts' into yashu-sso-about-help-updates
200455939-yashu Sep 6, 2024
d4906c4
Merge pull request #882 from portagenetwork/yashu-sso-about-help-updates
200455939-yashu Sep 6, 2024
268baa2
Address comments on PR
lagoan Sep 6, 2024
90625ab
Merge branch 'yashu-sso-link-accounts' of repo
lagoan Sep 6, 2024
936aae5
Update CHANGELOG
lagoan Sep 6, 2024
c973e16
Update CHANGELOG
lagoan Sep 6, 2024
8303962
Review changes 2 O
200455939-yashu Sep 9, 2024
8d3bd79
Resolving the conflicts after merging the link accounts branch
200455939-yashu Sep 9, 2024
bf0d279
Review changes 2.1
200455939-yashu Sep 10, 2024
ea4e25a
Removing conflict HEAD
200455939-yashu Sep 10, 2024
64e6ebd
Patch to put back ORCID linking functionality
aaronskiba Sep 10, 2024
b14a337
Fix broken test
lagoan Sep 10, 2024
f2bbd3e
Remove unused code in omniauth callbacks
lagoan Sep 10, 2024
57da037
Make rubocop happy
aaronskiba Sep 10, 2024
240aed5
Update CHANGELOG.md
aaronskiba Sep 10, 2024
5cfcf25
Merge pull request #891 from portagenetwork/aaron/issues/orcid-patch
aaronskiba Sep 10, 2024
a71c056
Merge pull request #869 from portagenetwork/yashu-controller-spec
aaronskiba Sep 11, 2024
d30ed72
Remove hyphen from "signing-up" string
aaronskiba Sep 11, 2024
e840763
Add rake task for `4.1.1+portage-4.2.0` upgrade
aaronskiba Sep 11, 2024
0f19dc3
Update CHANGELOG.md
aaronskiba Sep 11, 2024
2329cbe
Merge pull request #892 from portagenetwork/aaron/add-4.2.0-rake-upgr…
aaronskiba Sep 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,6 @@ jobs:

# Run the time consuming integration tests (using Chrome headless browser)
- name: 'Run Rspec Integration Tests'
run: bundle exec rspec spec/features/
run: |
bundle exec rspec spec/features/
bundle exec rspec spec/integration/
4 changes: 3 additions & 1 deletion .github/workflows/postgres.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,6 @@ jobs:

# Run the time consuming integration tests (using Chrome headless browser)
- name: 'Run Integration Tests'
run: bundle exec rspec spec/features/
run: |
bundle exec rspec spec/features/
bundle exec rspec spec/integration/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,15 @@

### Added

- Add Rake Upgrade Task For `4.1.1+portage-4.2.0` [#892](https://github.com/portagenetwork/roadmap/pull/892)

- Test cases for CILogon(openid_connection) changes in Omniauth controller - [#869](https://github.com/portagenetwork/roadmap/pull/869/)

- Implemented openid_connection SSO with CILogon [#872](https://github.com/portagenetwork/roadmap/pull/872)

- Create GET "/api/ca_dashboard/stats" endpoint to fetch Plan, User, and Org-related statistics [#852](https://github.com/portagenetwork/roadmap/pull/852)

- Added SSO changes updates to the About and Help pages. [#882](https://github.com/portagenetwork/roadmap/pull/882)

### Changed

Expand All @@ -18,6 +26,8 @@

- Fix flaky tests / Optimize Checking Of `plan.title` Within `spec/features/plans/exports_spec.rb` [#871](https://github.com/portagenetwork/roadmap/pull/871)

- Patch to Put Back ORCID Linking Functionality [#891](https://github.com/portagenetwork/roadmap/pull/891)

## [4.1.1+portage-4.1.3] - 2024-08-08

### Changed
Expand Down
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ gem 'omniauth-orcid'
# https://nvd.nist.gov/vuln/detail/CVE-2015-9284
gem 'omniauth-rails_csrf_protection'

# This gem provides cilogon support with devise login authentication
gem 'omniauth_openid_connect'

# A ruby implementation of the RFC 7519 OAuth JSON Web Token (JWT) standard.
gem 'jwt'

Expand Down
51 changes: 51 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ GEM
zeitwerk (~> 2.3)
addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
annotate_gem (0.0.14)
bundler (>= 1.1)
api-pagination (5.0.0)
ast (2.4.2)
attr_required (1.0.2)
autoprefixer-rails (10.4.16.0)
execjs (~> 2)
base64 (0.1.1)
Expand All @@ -91,6 +93,7 @@ GEM
rack (>= 0.9.0)
rouge (>= 1.0.0)
bigdecimal (3.1.8)
bindata (2.5.0)
bindex (0.8.1)
binding_of_caller (1.0.1)
debug_inspector (>= 1.2.0)
Expand Down Expand Up @@ -179,6 +182,8 @@ GEM
fog-aws
ecma-re-validator (0.4.0)
regexp_parser (~> 2.2)
email_validator (2.2.4)
activemodel
erubi (1.13.0)
excon (0.104.0)
execjs (2.9.1)
Expand All @@ -191,6 +196,8 @@ GEM
i18n (>= 1.8.11, < 2)
faraday (2.9.0)
faraday-net_http (>= 2.0, < 3.2)
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-http-cache (2.5.1)
faraday (>= 0.8)
faraday-net_http (3.1.0)
Expand Down Expand Up @@ -257,6 +264,13 @@ GEM
jsbundling-rails (1.3.0)
railties (>= 6.0.0)
json (2.7.2)
json-jwt (1.16.6)
activesupport (>= 4.2)
aes_key_wrap
base64
bindata
faraday (~> 2.0)
faraday-follow_redirects
json_schemer (0.2.25)
ecma-re-validator (~> 0.3)
hana (~> 1.3)
Expand Down Expand Up @@ -362,7 +376,23 @@ GEM
omniauth (~> 2.0)
omniauth-shibboleth (1.3.0)
omniauth (>= 1.0.0)
omniauth_openid_connect (0.7.1)
omniauth (>= 1.9, < 3)
openid_connect (~> 2.2)
open4 (1.3.4)
openid_connect (2.3.0)
activemodel
attr_required (>= 1.0.0)
email_validator
faraday (~> 2.0)
faraday-follow_redirects
json-jwt (>= 1.16)
mail
rack-oauth2 (~> 2.2)
swd (~> 2.0)
tzinfo
validate_url
webfinger (~> 2.0)
options (2.3.2)
orm_adapter (0.5.0)
parallel (1.24.0)
Expand Down Expand Up @@ -392,6 +422,13 @@ GEM
rack (>= 1.0, < 4)
rack-mini-profiler (3.3.1)
rack (>= 1.2.0)
rack-oauth2 (2.2.1)
activesupport
attr_required
faraday (~> 2.0)
faraday-follow_redirects
json-jwt (>= 1.11.0)
rack (>= 2.1.0)
rack-protection (3.2.0)
base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4)
Expand Down Expand Up @@ -520,6 +557,11 @@ GEM
activesupport (>= 5.2)
sprockets (>= 3.0.0)
strscan (3.1.0)
swd (2.0.3)
activesupport (>= 3)
attr_required (>= 0.0.5)
faraday (~> 2.0)
faraday-follow_redirects
syslog-logger (1.6.8)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
Expand All @@ -540,6 +582,9 @@ GEM
uniform_notifier (1.16.0)
uri (0.13.0)
uri_template (0.7.0)
validate_url (1.0.15)
activemodel (>= 3.0.0)
public_suffix
version_gem (1.1.3)
warden (1.2.9)
rack (>= 2.0.9)
Expand All @@ -548,6 +593,10 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webfinger (2.1.3)
activesupport
faraday (~> 2.0)
faraday-follow_redirects
webmock (3.23.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
Expand All @@ -569,6 +618,7 @@ GEM

PLATFORMS
arm64-darwin-21
arm64-darwin-22
x86_64-linux

DEPENDENCIES
Expand Down Expand Up @@ -616,6 +666,7 @@ DEPENDENCIES
omniauth-orcid
omniauth-rails_csrf_protection
omniauth-shibboleth
omniauth_openid_connect
parallel
pg
progress_bar
Expand Down
1 change: 1 addition & 0 deletions app/controllers/orgs_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def shibboleth_ds_passthru

end
end

# rubocop:enable Metrics/AbcSize

# POST /orgs (via AJAX from Org Typeaheads ... see below for specific pages)
Expand Down
65 changes: 58 additions & 7 deletions app/controllers/users/omniauth_callbacks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,62 @@
module Users
# Controller that handles callbacks from OmniAuth integrations (e.g. Shibboleth and ORCID)
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
##
# Dynamically build a handler for each omniauth provider
# -------------------------------------------------------------
IdentifierScheme.for_authentication.each do |scheme|
define_method(scheme.name.downcase) do
handle_omniauth(scheme)
# This is for the OpenidConnect CILogon

# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def openid_connect
# First or create
auth = request.env['omniauth.auth']
user = User.from_omniauth(auth)

if auth.info.email.nil? && user.nil?
# If email is missing we need to request the user to register with DMP.
# User email can be missing if the usFFvate or trusted clients only we won't get the value.
aaronskiba marked this conversation as resolved.
Show resolved Hide resolved
# User email id is one of the mandatory field which is must required.
flash[:notice] = _('Something went wrong, Please try signing up here.')
redirect_to new_user_registration_path
return
end

identifier_scheme = IdentifierScheme.find_by(name: auth.provider)

if current_user.nil?
# We need to register
if user.nil?
# Register and sign in
user = User.create_from_provider_data(auth)
user.identifiers << Identifier.create(identifier_scheme: identifier_scheme,
value: auth.uid,
attrs: auth,
identifiable: user)
end
sign_in_and_redirect user, event: :authentication
elsif user.nil?
# we need to link
current_user.identifiers << Identifier.create(identifier_scheme: identifier_scheme,
value: auth.uid,
attrs: auth,
identifiable: current_user)

flash[:notice] = _('Linked successfully')

redirect_to root_path
elsif user.id != current_user.id
# If a user was found but does NOT match the current user then the identifier has
# already been attached to another account (likely the user has 2 accounts)
flash[:alert] = format(_('The current %{description} iD has been already linked to a user with email %{email}'),
description: identifier_scheme.description, email: user.email)
redirect_to edit_user_registration_path
end
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength

def orcid
handle_omniauth(IdentifierScheme.for_authentication.find_by(name: 'orcid'))
end

def shibboleth
handle_omniauth(IdentifierScheme.for_authentication.find_by(name: 'shibboleth'))
end

# Processes callbacks from an omniauth provider and directs the user to
Expand All @@ -35,6 +84,7 @@ def handle_omniauth(scheme)
# If the uid didn't have a match in the system send them to register
if user.nil?
session["devise.#{scheme.name.downcase}_data"] = request.env['omniauth.auth']

redirect_to new_user_registration_url

# Otherwise sign them in
Expand Down Expand Up @@ -68,14 +118,15 @@ def handle_omniauth(scheme)
# If a user was found but does NOT match the current user then the identifier has
# already been attached to another account (likely the user has 2 accounts)
# rubocop:disable Layout/LineLength
flash[:alert] = _("The current #{scheme.description} iD has been already linked to a user with email #{identifier.user.email}")
flash[:alert] = format(_('The current %{scheme_description} iD has been already linked to a user with email %{identifier_user_email}'), scheme_description: scheme.description, identifier_user_email: identifier.user.email)
# rubocop:enable Layout/LineLength
end

# Redirect to the User Profile page
redirect_to edit_user_registration_path
end
end

# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity

Expand Down
6 changes: 1 addition & 5 deletions app/models/identifier_scheme.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ class IdentifierScheme < ApplicationRecord
# { "ror": "12345" }
# so we cannot allow spaces or non alpha characters!
def name=(value)
super(value&.downcase&.gsub(/[^a-z]/, ''))
super(value&.downcase&.gsub(/[^a-z|_]/, ''))
end

# ===========================
# = Instance Methods =
# ===========================
end
22 changes: 21 additions & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class User < ApplicationRecord
# :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :recoverable,
:rememberable, :trackable, :validatable, :omniauthable,
omniauth_providers: %i[shibboleth orcid]
omniauth_providers: %i[shibboleth orcid openid_connect]

##
# User Notification Preferences
Expand Down Expand Up @@ -182,6 +182,26 @@ def self.from_omniauth(auth)
.first&.identifiable
end

##
# Handle user creation from provider
# rubocop:disable Metrics/AbcSize
def self.create_from_provider_data(provider_data)
user = User.find_by email: provider_data.info.email

return user if user

User.create!(
firstname: provider_data.info&.first_name.present? ? provider_data.info.first_name : _('First name'),
surname: provider_data.info&.last_name.present? ? provider_data.info.last_name : _('Last name'),
email: provider_data.info.email,
# We don't know which organization to setup so we will use other
org: Org.find_by(is_other: true),
accept_terms: true,
password: Devise.friendly_token[0, 20]
)
end
aaronskiba marked this conversation as resolved.
Show resolved Hide resolved
# rubocop:enable Metrics/AbcSize

def self.to_csv(users)
User::AtCsv.new(users).to_csv
end
Expand Down
3 changes: 2 additions & 1 deletion app/views/devise/registrations/_external_identifier.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<% if id.nil? || id.value == '' %>

<% if scheme.name.downcase == 'orcid' %>
<%= link_to Rails.application.routes.url_helpers.send("user_orcid_omniauth_authorize_path"),
id: "connect-orcid-button",
Expand Down Expand Up @@ -88,4 +89,4 @@
data: {confirm: unlinkconf},
'aria-label': unlinktext,
'data-toggle': "tooltip" %>
<% end %>
<% end %>
21 changes: 21 additions & 0 deletions app/views/devise/registrations/_external_openid_connect.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<% if id.nil? || id.value == '' %>
<%# If we dont have an id we need to link %>
<i class="fas fa-user" title="<%= _('Institutional credentials') %>" aria-hidden="true"></i>
&nbsp;
<%= link_to _('Link your institutional credentials'),
Rails.application.routes.url_helpers.send("user_openid_connect_omniauth_authorize_path"),
title: _("Link your institutional credentials to access your account with them."),
'data-toggle': "tooltip", method: :post %>
<% else %>
<%# If We do have and id we need to present the option to unlink %>
<% unlinktext = _("Unlink your account from %{scheme_description}. You can link again at any time.") % { scheme_description: scheme.description} %>
<% unlinkconf = _("Are you sure you want to unlink %{scheme_description} ID?") % { scheme_description: scheme.description } %>
<%= id.value %>
aaronskiba marked this conversation as resolved.
Show resolved Hide resolved
<%= link_to '<i class="fas fa-fw fa-circle-xmark" aria-hidden="true"></i>'.html_safe,
destroy_user_identifier_path(id),
method: :delete,
title: unlinktext,
data: {confirm: unlinkconf},
'aria-label': unlinktext,
'data-toggle': "tooltip" %>
<% end %>
Loading
Loading