Class: KeeperSecretsManager::Notation::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/keeper_secrets_manager/notation.rb,
lib/keeper_secrets_manager/notation_enhancements.rb

Overview

Parse and resolve keeper:// notation URIs

Defined Under Namespace

Classes: NotationSection

Constant Summary collapse

ESCAPE_CHAR =
'\\'.freeze
ESCAPE_CHARS =

Characters that can be escaped

'/[]\\'.freeze

Instance Method Summary collapse

Constructor Details

#initialize(secrets_manager) ⇒ Parser

Returns a new instance of Parser.



10
11
12
# File 'lib/keeper_secrets_manager/notation.rb', line 10

def initialize(secrets_manager)
  @secrets_manager = secrets_manager
end

Instance Method Details

#download_file(notation) ⇒ Object

Convenience method to download file content directly



60
61
62
# File 'lib/keeper_secrets_manager/notation_enhancements.rb', line 60

def download_file(notation)
  get_value(notation, auto_process: true, auto_download: true)
end

#get_totp_code(notation) ⇒ Object

Convenience method to get TOTP code directly



55
56
57
# File 'lib/keeper_secrets_manager/notation_enhancements.rb', line 55

def get_totp_code(notation)
  get_value(notation, auto_process: true, generate_totp_code: true)
end

#get_value(notation, options = {}) ⇒ Object

Get value with enhanced functionality This method extends the basic parse method to handle special cases



8
9
10
11
12
13
14
15
16
17
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
43
44
45
46
47
48
49
50
51
52
# File 'lib/keeper_secrets_manager/notation_enhancements.rb', line 8

def get_value(notation, options = {})
  value = parse(notation)

  # Check if we should process special types
  return value unless options[:auto_process]

  # Parse the notation to understand what we're dealing with
  parsed = parse_notation(notation)
  return value if parsed.length < 3

  selector = parsed[2].text&.first
  return value unless selector

  case selector.downcase
  when 'file'
    # If it's a file and auto_download is enabled, download it
    if options[:auto_download] && value.is_a?(Hash) && value['fileUid']
      begin
        file_data = @secrets_manager.download_file(value['fileUid'])
        return file_data['data'] # Return file content
      rescue StandardError => e
        raise NotationError, "Failed to download file: #{e.message}"
      end
    end

  when 'field'
    # Check if it's a TOTP field
    parameter = parsed[2].parameter&.first
    if parameter && parameter.downcase == 'onetimecode' && value.is_a?(String) && value.start_with?('otpauth://') && (options[:generate_totp_code])
      begin
        totp_params = TOTP.parse_url(value)
        return TOTP.generate_code(
          totp_params['secret'],
          algorithm: totp_params['algorithm'],
          digits: totp_params['digits'],
          period: totp_params['period']
        )
      rescue StandardError => e
        raise NotationError, "Failed to generate TOTP code: #{e.message}"
      end
    end
  end

  value
end

#parse(notation) ⇒ Object

Parse notation and return value

Raises:



15
16
17
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/keeper_secrets_manager/notation.rb', line 15

def parse(notation)
  return nil if notation.nil? || notation.empty?

  # Validate notation format before parsing
  raise NotationError, 'Invalid notation format: must be a string' unless notation.is_a?(String)

  # Parse notation URI
  begin
    parsed = parse_notation(notation)
  rescue StandardError => e
    raise NotationError, "Invalid notation format: #{e.message}"
  end

  # Validate we have minimum required sections
  raise NotationError, "Invalid notation: #{notation}" if parsed.length < 3

  # Extract components
  record_token = parsed[1].text&.first
  selector = parsed[2].text&.first

  raise NotationError, 'Invalid notation: missing record' unless record_token
  raise NotationError, 'Invalid notation: missing selector' unless selector

  # Get record
  records = @secrets_manager.get_secrets([record_token])

  # If not found by UID, try by title
  if records.empty?
    all_records = @secrets_manager.get_secrets
    records = all_records.select { |r| r.title == record_token }
  end

  raise NotationError, "Multiple records match '#{record_token}'" if records.size > 1
  raise NotationError, "No records match '#{record_token}'" if records.empty?

  record = records.first

  # Extract parameters
  parameter = parsed[2].parameter&.first
  index1 = parsed[2].index1&.first
  index2 = parsed[2].index2&.first

  # Process selector
  case selector.downcase
  when 'type'
    record.type
  when 'title'
    record.title
  when 'notes'
    record.notes
  when 'file'
    handle_file_selector(record, parameter, record_token)
  when 'field', 'custom_field'
    handle_field_selector(record, selector, parameter, index1, index2, parsed[2])
  else
    raise NotationError, "Invalid selector: #{selector}"
  end
end