saml-kit 0.2.17 → 0.2.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e7c4ec8a1921995d89ce16ce8161b20763cb0b3
4
- data.tar.gz: fc40b97c8a0da3edd71c1470867087cde7a11750
3
+ metadata.gz: 1523b17e9ef3134f4d110be787f4a63f600fcaff
4
+ data.tar.gz: f69357bc2618c53524aab2061fb1dec4044f210c
5
5
  SHA512:
6
- metadata.gz: 192bc0456858f0efefd328fdf666cb335aeb84c8e1b83bc993925526263381e5ee5d5547bc6fc47dcaee72f2b4fa46dfc386abcc8adb560f07d87f0c09618a5f
7
- data.tar.gz: be310f063a48d7108c2d6ed35090ccc5d7fcbc9858941e5ec9f5571a1e369ed90e2991a20025bb9e58a3f4a0bab836005be9652a7ab55f4192022c85ed66a982
6
+ metadata.gz: e2cd4dc82b98c2c763edf356607efda096740760b131c89b9a249bee156d9e0dc314c321a087047a99a36850c561d44cb3f3fe3c7b761fa8e9ad265236eba1b2
7
+ data.tar.gz: f7c66a56428fd31d7919881b8c55f0711068ef33c0afc1db30cdf2cbb110766fdf5a80c279b62d2143ee88cdaaa1b867c71fb62fb6f1932f27ceebe0b94a96b8
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Saml::Kit
1
+ ![SAML-Kit](https://github.com/saml-kit/saml-kit/raw/master/spec/examples/saml-kit.gif)
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/saml-kit.svg)](https://rubygems.org/gems/saml-kit)
4
+ [![Code Climate](https://codeclimate.com/github/saml-kit/saml-kit.svg)](https://codeclimate.com/github/saml-kit/saml-kit)
5
+ [![Build Status](https://travis-ci.org/saml-kit/saml-kit.svg)](https://travis-ci.org/saml-kit/saml-kit)
2
6
 
3
7
  Saml::Kit is a library with the purpose of creating and consuming SAML
4
8
  documents. It supports the HTTP Post and HTTP Redirect bindings. It can
@@ -30,7 +34,8 @@ To specify a global configuration: (useful for a rails application)
30
34
  Saml::Kit.configure do |configuration|
31
35
  configuration.issuer = ENV['ISSUER']
32
36
  configuration.generate_key_pair_for(use: :signing)
33
- configuration.generate_key_pair_for(use: :signing)
37
+ configuration.add_key_pair(ENV["CERTIFICATE"], ENV["PRIVATE_KEY"], passphrase: ENV['PASSPHRASE'], use: :signing)
38
+ configuration.generate_key_pair_for(use: :encryption)
34
39
  end
35
40
  ```
36
41
 
@@ -216,8 +221,8 @@ class User
216
221
  end
217
222
 
218
223
  user = User.new(id: SecureRandom.uuid, email: "hello@example.com")
219
- sp = Saml::Kit::IdentityProviderMetadata.new(xml)
220
- url, saml_params = sp.logout_request_for(user, binding: :http_post)
224
+ idp = Saml::Kit::IdentityProviderMetadata.new(xml)
225
+ url, saml_params = idp.logout_request_for(user, binding: :http_post)
221
226
  puts [url, saml_params].inspect
222
227
  # ["https://www.example.com/logout", {"SAMLRequest"=>"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48TG9nb3V0UmVxdWVzdCBJRD0iXzg3NjZiNTYyLTc2MzQtNDU4Zi04MzJmLTE4ODkwMjRlZDQ0MyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTctMTItMTlUMDQ6NTg6MThaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20vbG9nb3V0IiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIi8+PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5kODc3YWEzZS01YTUyLTRhODAtYTA3ZC1lM2U5YzBjNTA1Nzk8L05hbWVJRD48L0xvZ291dFJlcXVlc3Q+"}]
223
228
  ```
@@ -27,12 +27,12 @@ module Saml
27
27
  xml_hash ? Signature.new(xml_hash) : nil
28
28
  end
29
29
 
30
- def expired?
31
- Time.current > expired_at
30
+ def expired?(now = Time.current)
31
+ now > expired_at
32
32
  end
33
33
 
34
- def active?
35
- Time.current > started_at && !expired?
34
+ def active?(now = Time.current)
35
+ now > configuration.clock_drift.before(started_at) && !expired?
36
36
  end
37
37
 
38
38
  def attributes
@@ -69,9 +69,11 @@ module Saml
69
69
 
70
70
  private
71
71
 
72
+ attr_reader :configuration
73
+
72
74
  def assertion
73
75
  @assertion ||= if encrypted?
74
- decrypted = XmlDecryption.new(configuration: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
76
+ decrypted = XmlDecryption.new(configuration: configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
75
77
  Saml::Kit.logger.debug(decrypted)
76
78
  Hash.from_xml(decrypted)['Assertion']
77
79
  else
@@ -91,7 +93,7 @@ module Saml
91
93
  end
92
94
 
93
95
  def must_match_issuer
94
- unless audiences.include?(@configuration.issuer)
96
+ unless audiences.include?(configuration.issuer)
95
97
  errors[:audience] << error_message(:must_match_issuer)
96
98
  end
97
99
  end
@@ -13,6 +13,10 @@ module Saml
13
13
  # <saml:Issuer>Day of the Dangerous Cousins</saml:Issuer>
14
14
  # <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
15
15
  # </samlp:AuthnRequest>
16
+ #
17
+ # Example:
18
+ #
19
+ # {include:file:spec/examples/authentication_request_spec.rb}
16
20
  class AuthenticationRequest < Document
17
21
  include Requestable
18
22
 
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Bindings
4
+ # {include:file:spec/saml/bindings/binding_spec.rb}
4
5
  class Binding
5
6
  attr_reader :binding, :location
6
7
 
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Bindings
4
+ # {include:file:spec/saml/bindings/http_post_spec.rb}
4
5
  class HttpPost < Binding
5
6
  include Serializable
6
7
 
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Bindings
4
+ # {include:file:spec/saml/bindings/http_redirect_spec.rb}
4
5
  class HttpRedirect < Binding
5
6
  include Serializable
6
7
 
@@ -31,21 +32,26 @@ module Saml
31
32
 
32
33
  def ensure_valid_signature!(params, document)
33
34
  return if params[:Signature].blank? || params[:SigAlg].blank?
34
-
35
- signature = decode(params[:Signature])
36
- canonical_form = [:SAMLRequest, :SAMLResponse, :RelayState, :SigAlg].map do |key|
37
- value = params[key]
38
- value.present? ? "#{key}=#{value}" : nil
39
- end.compact.join('&')
40
-
41
35
  return if document.provider.nil?
42
- if document.provider.verify(algorithm_for(params[:SigAlg]), signature, canonical_form)
36
+
37
+ if document.provider.verify(
38
+ algorithm_for(params[:SigAlg]),
39
+ decode(params[:Signature]),
40
+ canonicalize(params)
41
+ )
43
42
  document.signature_verified!
44
43
  else
45
44
  raise ArgumentError.new("Invalid Signature")
46
45
  end
47
46
  end
48
47
 
48
+ def canonicalize(params)
49
+ [:SAMLRequest, :SAMLResponse, :RelayState, :SigAlg].map do |key|
50
+ value = params[key]
51
+ value.present? ? "#{key}=#{value}" : nil
52
+ end.compact.join('&')
53
+ end
54
+
49
55
  def algorithm_for(algorithm)
50
56
  case algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
51
57
  when 256
@@ -60,20 +66,13 @@ module Saml
60
66
  end
61
67
 
62
68
  def normalize(params)
63
- if params.respond_to? :inject
64
- params.inject({}) do |memo, (key, value)|
65
- memo[key.to_sym] = value
66
- memo
67
- end
68
- else
69
- {
70
- SAMLRequest: params['SAMLRequest'] || params[:SAMLRequest],
71
- SAMLResponse: params['SAMLResponse'] || params[:SAMLResponse],
72
- RelayState: params['RelayState'] || params[:RelayState],
73
- Signature: params['Signature'] || params[:Signature],
74
- SigAlg: params['SigAlg'] || params[:SigAlg],
75
- }
76
- end
69
+ {
70
+ SAMLRequest: params['SAMLRequest'] || params[:SAMLRequest],
71
+ SAMLResponse: params['SAMLResponse'] || params[:SAMLResponse],
72
+ RelayState: params['RelayState'] || params[:RelayState],
73
+ Signature: params['Signature'] || params[:Signature],
74
+ SigAlg: params['SigAlg'] || params[:SigAlg],
75
+ }
77
76
  end
78
77
  end
79
78
  end
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Bindings
4
+ # {include:file:spec/saml/bindings/url_builder_spec.rb}
4
5
  class UrlBuilder
5
6
  include Serializable
6
7
  attr_reader :configuration
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/authentication_request_spec.rb}
4
5
  class AuthenticationRequest
5
6
  include Saml::Kit::Templatable
6
7
  attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/identity_provider_metadata_spec.rb}
4
5
  class IdentityProviderMetadata
5
6
  include Saml::Kit::Templatable
6
7
  extend Forwardable
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/logout_request_spec.rb}
4
5
  class LogoutRequest
5
6
  include Saml::Kit::Templatable
6
7
  attr_accessor :id, :destination, :issuer, :name_id_format, :now
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/logout_response_spec.rb}
4
5
  class LogoutResponse
5
6
  include Saml::Kit::Templatable
6
7
  attr_accessor :id, :issuer, :version, :status_code, :now, :destination
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/metadata_spec.rb}
4
5
  class Metadata
5
6
  include Templatable
6
7
 
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/response_spec.rb}
4
5
  class Response
5
6
  include Templatable
6
7
  attr_reader :user, :request
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/service_provider_metadata_spec.rb}
4
5
  class ServiceProviderMetadata
5
6
  include Saml::Kit::Templatable
6
7
  extend Forwardable
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/saml/certificate_spec.rb}
3
4
  class Certificate
4
5
  BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
5
6
  END_CERT=/-----END CERTIFICATE-----/
@@ -32,14 +32,17 @@ module Saml
32
32
  attr_accessor :session_timeout
33
33
  # The logger to write log messages to.
34
34
  attr_accessor :logger
35
+ # The total allowable clock drift for session timeout validation.
36
+ attr_accessor :clock_drift
35
37
 
36
38
  def initialize # :yields configuration
37
- @signature_method = :SHA256
39
+ @clock_drift = 30.seconds
38
40
  @digest_method = :SHA256
41
+ @key_pairs = []
42
+ @logger = Logger.new(STDOUT)
39
43
  @registry = DefaultRegistry.new
40
44
  @session_timeout = 3.hours
41
- @logger = Logger.new(STDOUT)
42
- @key_pairs = []
45
+ @signature_method = :SHA256
43
46
  yield self if block_given?
44
47
  end
45
48
 
@@ -26,7 +26,8 @@ module Saml
26
26
  # configuration.registry = OnDemandRegistry.new(configuration.registry)
27
27
  # configuration.logger = Rails.logger
28
28
  # end
29
-
29
+ #
30
+ # {include:file:spec/saml/default_registry.rb}
30
31
  class DefaultRegistry
31
32
  def initialize(items = {})
32
33
  @items = items
@@ -17,37 +17,36 @@ module Saml
17
17
  @configuration = configuration
18
18
  @content = xml
19
19
  @name = name
20
- @xml_hash = Hash.from_xml(xml) || {}
21
20
  end
22
21
 
23
22
  # Returns the ID for the SAML document.
24
23
  def id
25
- to_h.fetch(name, {}).fetch('ID', nil)
24
+ root.fetch('ID', nil)
26
25
  end
27
26
 
28
27
  # Returns the Issuer for the SAML document.
29
28
  def issuer
30
- to_h.fetch(name, {}).fetch('Issuer', nil)
29
+ root.fetch('Issuer', nil)
31
30
  end
32
31
 
33
32
  # Returns the Version of the SAML document.
34
33
  def version
35
- to_h.fetch(name, {}).fetch('Version', {})
34
+ root.fetch('Version', {})
36
35
  end
37
36
 
38
37
  # Returns the Destination of the SAML document.
39
38
  def destination
40
- to_h.fetch(name, {}).fetch('Destination', nil)
39
+ root.fetch('Destination', nil)
41
40
  end
42
41
 
43
42
  # Returns the Destination of the SAML document.
44
43
  def issue_instant
45
- Time.parse(to_h[name]['IssueInstant'])
44
+ Time.parse(root['IssueInstant'])
46
45
  end
47
46
 
48
47
  # Returns the SAML document returned as a Hash.
49
48
  def to_h
50
- @xml_hash
49
+ @xml_hash ||= Hash.from_xml(content) || {}
51
50
  end
52
51
 
53
52
  # Returns the SAML document as an XML string.
@@ -68,24 +67,28 @@ module Saml
68
67
  end
69
68
 
70
69
  class << self
70
+ XPATH = [
71
+ "/samlp:AuthnRequest",
72
+ "/samlp:LogoutRequest",
73
+ "/samlp:LogoutResponse",
74
+ "/samlp:Response",
75
+ ].join("|")
76
+
71
77
  # Returns the raw xml as a Saml::Kit SAML document.
72
78
  #
73
79
  # @param xml [String] the raw xml string.
74
80
  # @param configuration [Saml::Kit::Configuration] the configuration to use for unpacking the document.
75
81
  def to_saml_document(xml, configuration: Saml::Kit.configuration)
76
- hash = Hash.from_xml(xml)
77
- if hash['Response'].present?
78
- Response.new(xml, configuration: configuration)
79
- elsif hash['LogoutResponse'].present?
80
- LogoutResponse.new(xml, configuration: configuration)
81
- elsif hash['AuthnRequest'].present?
82
- AuthenticationRequest.new(xml, configuration: configuration)
83
- elsif hash['LogoutRequest'].present?
84
- LogoutRequest.new(xml, configuration: configuration)
85
- end
82
+ constructor = {
83
+ "AuthnRequest" => Saml::Kit::AuthenticationRequest,
84
+ "LogoutRequest" => Saml::Kit::LogoutRequest,
85
+ "LogoutResponse" => Saml::Kit::LogoutResponse,
86
+ "Response" => Saml::Kit::Response,
87
+ }[Saml::Kit::Xml.new(xml).find_by(XPATH).name] || InvalidDocument
88
+ constructor.new(xml, configuration: configuration)
86
89
  rescue => error
87
90
  Saml::Kit.logger.error(error)
88
- InvalidDocument.new(xml)
91
+ InvalidDocument.new(xml, configuration: configuration)
89
92
  end
90
93
 
91
94
  # @!visibility private
@@ -109,18 +112,19 @@ module Saml
109
112
 
110
113
  attr_reader :content, :name, :configuration
111
114
 
115
+ def root
116
+ to_h.fetch(name, {})
117
+ end
118
+
112
119
  def must_match_xsd
113
120
  matches_xsd?(PROTOCOL_XSD)
114
121
  end
115
122
 
116
123
  def must_be_expected_type
117
- return if to_h.nil?
118
-
119
124
  errors[:base] << error_message(:invalid) unless expected_type?
120
125
  end
121
126
 
122
127
  def expected_type?
123
- return false if to_xml.blank?
124
128
  to_h[name].present?
125
129
  end
126
130
 
@@ -6,6 +6,8 @@ module Saml
6
6
  #
7
7
  # puts Saml::Kit::Fingerprint.new(certificate).to_s
8
8
  # # B7:AB:DC:BD:4D:23:58:65:FD:1A:99:0C:5F:89:EA:87:AD:F1:D7:83:34:7A:E9:E4:88:12:DD:46:1F:38:05:93
9
+ #
10
+ # {include:file:spec/saml/fingerprint_spec.rb}
9
11
  class Fingerprint
10
12
  # The OpenSSL::X509::Certificate
11
13
  attr_reader :x509
@@ -26,6 +26,10 @@ module Saml
26
26
  # puts metadata.to_xml
27
27
  #
28
28
  # For more details on generating metadata see {Saml::Kit::Metadata}.
29
+ #
30
+ # Example:
31
+ #
32
+ # {include:file:spec/examples/identity_provider_metadata_spec.rb}
29
33
  class IdentityProviderMetadata < Metadata
30
34
  def initialize(xml)
31
35
  super("IDPSSODescriptor", xml)
@@ -1,13 +1,20 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/saml/invalid_document_spec.rb}
3
4
  class InvalidDocument < Document
4
5
  validate do |model|
5
6
  model.errors[:base] << model.error_message(:invalid)
6
7
  end
7
8
 
8
- def initialize(xml)
9
+ def initialize(xml, configuration: nil)
9
10
  super(xml, name: "InvalidDocument")
10
11
  end
12
+
13
+ def to_h
14
+ super
15
+ rescue
16
+ {}
17
+ end
11
18
  end
12
19
  end
13
20
  end
@@ -20,6 +20,8 @@ module Saml
20
20
  # url, saml_params = document.response_for(binding: :http_post)
21
21
  #
22
22
  # See {#response_for} for more information.
23
+ #
24
+ # {include:file:spec/examples/logout_request_spec.rb}
23
25
  class LogoutRequest < Document
24
26
  include Requestable
25
27
  validates_presence_of :single_logout_service, if: :expected_type?
@@ -3,6 +3,8 @@ module Saml
3
3
  # This class is used to parse a LogoutResponse SAML document.
4
4
  #
5
5
  # document = Saml::Kit::LogoutResponse.new(raw_xml)
6
+ #
7
+ # {include:file:spec/examples/logout_response_spec.rb}
6
8
  class LogoutResponse < Document
7
9
  include Respondable
8
10
 
@@ -21,6 +21,7 @@ module Saml
21
21
  #
22
22
  # See {Saml::Kit::Builders::ServiceProviderMetadata} and {Saml::Kit::Builders::IdentityProviderMetadata}
23
23
  # for a list of options that can be specified.
24
+ # {include:file:spec/examples/metadata_spec.rb}
24
25
  class Metadata
25
26
  METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
26
27
  include ActiveModel::Validations
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/examples/response_spec.rb}
3
4
  class Response < Document
4
5
  include Respondable
5
6
  extend Forwardable
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/examples/service_provider_metadata_spec.rb}
3
4
  class ServiceProviderMetadata < Metadata
4
5
  def initialize(xml)
5
6
  super("SPSSODescriptor", xml)
@@ -1,5 +1,5 @@
1
1
  module Saml
2
2
  module Kit
3
- VERSION = "0.2.17"
3
+ VERSION = "0.2.18"
4
4
  end
5
5
  end
data/lib/saml/kit/xml.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/saml/xml_spec.rb}
3
4
  class Xml # :nodoc:
4
5
  include ActiveModel::Validations
5
6
  NAMESPACES = {
@@ -59,24 +60,20 @@ module Saml
59
60
  end
60
61
 
61
62
  def validate_certificates(now = Time.current)
62
- return unless document.at_xpath('//ds:Signature', Xmldsig::NAMESPACES).present?
63
+ return if find_by('//ds:Signature').nil?
63
64
 
64
65
  x509_certificates.each do |certificate|
65
- if now < certificate.not_before
66
- errors.add(:certificate, "Not valid before #{certificate.not_before}")
67
- end
66
+ inactive = now < certificate.not_before
67
+ errors.add(:certificate, "Not valid before #{certificate.not_before}") if inactive
68
68
 
69
- if now > certificate.not_after
70
- errors.add(:certificate, "Not valid after #{certificate.not_after}")
71
- end
69
+ expired = now > certificate.not_after
70
+ errors.add(:certificate, "Not valid after #{certificate.not_after}") if expired
72
71
  end
73
72
  end
74
73
 
75
74
  def x509_certificates
76
75
  xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
77
- document.search(xpath, Xmldsig::NAMESPACES).map do |item|
78
- Certificate.to_x509(item.text)
79
- end
76
+ find_all(xpath).map { |item| Certificate.to_x509(item.text) }
80
77
  end
81
78
  end
82
79
  end
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/saml/xml_decryption_spec.rb}
3
4
  class XmlDecryption
4
5
  # The list of private keys to use to attempt to decrypt the document.
5
6
  attr_reader :private_keys
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.17
4
+ version: 0.2.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - mo khan
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-22 00:00:00.000000000 Z
11
+ date: 2017-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel