Class: Saro::Dat::DatCmsManager

Inherits:
Object
  • Object
show all
Defined in:
lib/saro/dat/dat_cms_manager.rb

Constant Summary collapse

DAT_CMS_API_VERSION =
"v1"
STOP_JOIN_TIMEOUT_SECONDS =
1.0

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri:, token:, interval_seconds: 60, verify_only: false, dat_manager: nil, connect_timeout_seconds: 5, sync_timeout_seconds: 15) ⇒ DatCmsManager

Returns a new instance of DatCmsManager.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/saro/dat/dat_cms_manager.rb', line 18

def initialize(uri:, token:, interval_seconds: 60, verify_only: false, dat_manager: nil,
               connect_timeout_seconds: 5, sync_timeout_seconds: 15)
  @uri = uri
  @token = token
  @interval_seconds = interval_seconds
  @verify_only = verify_only
  @manager = dat_manager || DatManager.new
  @connect_timeout_seconds = connect_timeout_seconds
  @sync_timeout_seconds = sync_timeout_seconds
  @active_http = nil
  @version = 0
  @lock = Mutex.new
  @lifecycle = Mutex.new
  @stop_cond = ConditionVariable.new
  @stopped = false
  @logger = Logger.new($stdout)
  @logger.level = Logger::DEBUG
  @last_error = Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_NOT_SYNCED)

  sync

  if @interval_seconds > 0
    schedule_sync
  end
end

Instance Attribute Details

#last_errorObject (readonly)

Returns the value of attribute last_error.



61
62
63
# File 'lib/saro/dat/dat_cms_manager.rb', line 61

def last_error
  @last_error
end

Class Method Details

.builderObject



172
173
174
# File 'lib/saro/dat/dat_cms_manager.rb', line 172

def self.builder
  DatCmsManagerBuilder.new
end

.http_status_error(status) ⇒ Object



150
151
152
153
154
155
156
157
158
# File 'lib/saro/dat/dat_cms_manager.rb', line 150

def self.http_status_error(status)
  case status
  when 401 then Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_UNAUTHORIZED, "http 401")
  when 403 then Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_FORBIDDEN, "http 403")
  when 404 then Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_ENDPOINT_NOT_FOUND, "http 404")
  when 500..599 then Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_SERVER_ERROR, "http #{status}")
  else Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_HTTP_STATUS, "http #{status}")
  end
end

Instance Method Details

#get_managerObject



160
161
162
# File 'lib/saro/dat/dat_cms_manager.rb', line 160

def get_manager
  @manager
end

#issue(plain, secure) ⇒ Object



164
165
166
# File 'lib/saro/dat/dat_cms_manager.rb', line 164

def issue(plain, secure)
  @manager.issue(plain, secure)
end

#parse(dat) ⇒ Object



168
169
170
# File 'lib/saro/dat/dat_cms_manager.rb', line 168

def parse(dat)
  @manager.parse(dat)
end

#stopObject



44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/saro/dat/dat_cms_manager.rb', line 44

def stop
  thread = nil
  @lifecycle.synchronize do
    return if @stopped
    @stopped = true
    @stop_cond.broadcast
    thread = @thread
    @active_http&.finish if @active_http&.started?
  end
  thread&.join(STOP_JOIN_TIMEOUT_SECONDS)
  nil
end

#stopped?Boolean

Returns:

  • (Boolean)


57
58
59
# File 'lib/saro/dat/dat_cms_manager.rb', line 57

def stopped?
  @lifecycle.synchronize { @stopped }
end

#syncObject



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/saro/dat/dat_cms_manager.rb', line 67

def sync
  err = sync_or_raise
  @last_error = nil
  err
rescue Saro::Dat::Error => e
  unless e.retry == :state
    @last_error = e
    @logger.error("[CRITICAL] DAT CMS SYNC #{@uri}: #{e.code} #{e.detail}")
  end
  nil
rescue StandardError => e
  @last_error = Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_UNKNOWN, "unclassified cms failure", cause: e)
  @logger.error("[CRITICAL] DAT CMS SYNC #{@uri}: #{e.message}")
  nil
end

#sync_or_raiseObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/saro/dat/dat_cms_manager.rb', line 83

def sync_or_raise
  unless @lock.try_lock
    @logger.debug("cms sync skipped, previous sync still running: #{@uri}")
    raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_SYNC_IN_PROGRESS)
  end

  begin
    url = URI("#{@uri}?version=#{@version}")
    request = Net::HTTP::Get.new(url)
    request["Authorization"] = @token

    response =
      begin
        Net::HTTP.start(url.host, url.port, use_ssl: url.scheme == 'https',
                        open_timeout: (@connect_timeout_seconds.zero? ? nil : @connect_timeout_seconds),
                        read_timeout: (@sync_timeout_seconds.zero? ? nil : @sync_timeout_seconds)) do |http|
          @lifecycle.synchronize { @active_http = http }
          http.request(request)
        ensure
          @lifecycle.synchronize { @active_http = nil }
        end
      rescue StandardError => e
        raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_UNREACHABLE, "cannot reach #{@uri}", cause: e)
      end

    status = response.code.to_i
    raise self.class.http_status_error(status) unless status.between?(200, 299)

    body = response.body
    raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_MALFORMED, "response is empty") if body.nil? || body.empty?
    unless body.ascii_only?
      raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_MALFORMED, "response is not strict ASCII")
    end

    lines = body.split("\n", 2)
    new_version_str = lines[0]
    new_certificates = lines.length == 2 ? lines[1].strip : ""

    unless new_version_str.match?(/\A[0-9]+\z/)
      raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_MALFORMED, "version line is not a plain decimal integer")
    end
    new_version = new_version_str.to_i

    if new_certificates.empty?
      @logger.debug("No new certificate: #{url}")
      return nil
    end

    if new_version < @version
      @logger.warn("#{Saro::Dat::ErrorCode::CMS_VERSION_RESET}: #{@version} -> #{new_version}")
    end

    renew_count =
      begin
        @manager.imports(new_certificates, clear: false)
      rescue Saro::Dat::Error => e
        raise Saro::Dat::Error.new(Saro::Dat::ErrorCode::CMS_IMPORT_FAILED, "cannot apply received certificates", cause: e)
      end

    @version = new_version
    @logger.debug("Renewed #{renew_count} certificates for version #{new_version}: #{url}")
    nil
  ensure
    @lock.unlock
  end
end

#versionObject



63
64
65
# File 'lib/saro/dat/dat_cms_manager.rb', line 63

def version
  @version
end