class Google::Cloud::Gemserver::Authentication

# Authentication

Manages the permissions of the currently logged in user with the gcloud sdk.

Attributes

proj[RW]

The project id of the Google App Engine project the gemserver was deployed to. @return [String]

Public Class Methods

new() click to toggle source

Creates the Authentication object and sets the project id field.

# File lib/google/cloud/gemserver/authentication.rb, line 41
def initialize
  @proj = Configuration.new[:proj_id]
end

Public Instance Methods

access_token() click to toggle source

Generates an access token from a user authenticated by gcloud.

@return [String]

# File lib/google/cloud/gemserver/authentication.rb, line 67
def access_token
  return unless can_modify?
  scope = ["https://www.googleapis.com/auth/cloud-platform"]
  auth = Google::Auth.get_application_default scope
  auth.fetch_access_token!
end
can_modify?() click to toggle source

Checks if the currently logged in user can modify the gemserver i.e. create keys.

@return [Boolean]

# File lib/google/cloud/gemserver/authentication.rb, line 50
def can_modify?
  user = curr_user
  owners.each do |owner|
    return true if extract_account(owner) == user
  end
  editors.each do |editor|
    return true if extract_account(editor) == user
  end
  puts "You are either not authenticated with gcloud or lack access" \
    " to the gemserver."
  false
end
validate_token(auth_header) click to toggle source

@private Implicitly checks if the account that generated the token has edit permissions on the Google Cloud Platform project by issuing a redundant update to the project (update to original settings).

@param [String] The authentication token generated from gcloud.

@return [Boolean]

# File lib/google/cloud/gemserver/authentication.rb, line 82
def validate_token auth_header
  token = auth_header.split.drop(1)[0]

  appengine_url = "https://appengine.googleapis.com"
  endpoint = "/v1/apps/#{@proj}/services/default?updateMask=split"
  version = appengine_version token
  split = {
    "split" => {
      "allocations" => {
        version.to_s => 1
      }
    }
  }
  res = send_req appengine_url, endpoint, Net::HTTP::Patch, token, split
  if check_status(res)
    op = JSON.parse(res.body)["name"]
    wait_for_op appengine_url, "/v1/#{op}", token
    true
  else
    false
  end
end

Private Instance Methods

appengine_version(token) click to toggle source

@private Fetches the latest version of the deployed Google App Engine instance running the gemserver (default service only).

@param [String] The authentication token generated from gcloud.

@return [String]

# File lib/google/cloud/gemserver/authentication.rb, line 114
def appengine_version token
  appengine_url = "https://appengine.googleapis.com"
  path = "/v1/apps/#{@proj}/services/default"
  res = send_req appengine_url, path, Net::HTTP::Get, token

  fail "Unauthorized" unless check_status(res)

  JSON.parse(res.body)["split"]["allocations"].first[0]
end
check_status(response, code = 200) click to toggle source

@private Checks if a request response matches a given status code.

@param [Net::HTTPResponse] reponse The response from a request.

@param [Integer] code The desired response code.

@return [Boolean]

# File lib/google/cloud/gemserver/authentication.rb, line 188
def check_status response, code = 200
  response.code.to_i == code
end
curr_user() click to toggle source

@private Fetches the active account of the currently logged in user.

@return [String]

# File lib/google/cloud/gemserver/authentication.rb, line 226
def curr_user
  raw = run_cmd "gcloud auth list --format json"
  JSON.load(raw).map do |i|
    return i["account"] if i["status"] == "ACTIVE"
  end
  abort "You are not authenticated with gcloud"
end
editors() click to toggle source

@private Fetches members with a role of editor that can access the gemserver.

@return [Array]

# File lib/google/cloud/gemserver/authentication.rb, line 209
def editors
  members "roles/editor"
end
extract_account(acc) click to toggle source

@private Parses a gcloud “member” and removes the account prefix.

@param [String] acc The member the account is extracted from.

@return [String]

# File lib/google/cloud/gemserver/authentication.rb, line 240
def extract_account acc
  acc[acc.index(":") + 1 .. acc.size]
end
members(type) click to toggle source

@private Fetches the members with a specific role that have access to the Google App Engine project the gemserver was deployed to.

@return [Array]

# File lib/google/cloud/gemserver/authentication.rb, line 197
def members type
  yml = YAML.load(run_cmd "gcloud projects get-iam-policy #{@proj}")
  yml["bindings"].select do |member_set|
    member_set["role"] == type
  end[0]["members"]
end
owners() click to toggle source

@private Fetches members with a role of owner that can access the gemserver.

@return [Array]

# File lib/google/cloud/gemserver/authentication.rb, line 218
def owners
  members "roles/owner"
end
run_cmd(args) click to toggle source

@private Runs a given command on the local machine.

@param [String] args The command to be run.

# File lib/google/cloud/gemserver/authentication.rb, line 248
def run_cmd args
  `#{args}`
end
send_req(dom, path, type, token, params = nil) click to toggle source

@private Sends a request to a given URL with given parameters.

@param [String] dom The protocol + domain name of the request.

@param [String] path The path of the URL.

@param [Net::HTTP] type The type of request to be made.

@param [String] token The authentication token used in the header.

@param [Hash] params Additional parameters send in the request body.

@return [Net::HTTPResponse]

# File lib/google/cloud/gemserver/authentication.rb, line 138
def send_req dom, path, type, token, params = nil
  uri = URI.parse dom
  http = Net::HTTP.new uri.host, uri.port
  http.use_ssl = true if dom.include? "https"

  req = type.new path
  req["Authorization"] = Signet::OAuth2.generate_bearer_authorization_header token
  unless type == Net::HTTP::Get
    if params
      req["Content-Type"] = "application/json"
      req.body = params.to_json
    end
  end
  http.request req
end
wait_for_op(dom, path, token, timeout = 60) click to toggle source

@private Waits for a project update operation to complete.

@param [String] dom The domain and protocol of the request.

@param [String] path The path of the request containing the operation ID.

@param [String] token The authorization token in the request.

@param [Integer] timeout The length of time the operation is polled.

# File lib/google/cloud/gemserver/authentication.rb, line 165
def wait_for_op dom, path, token, timeout = 60
  start = Time.now
  loop do
    if Time.now - start > timeout
      fail "Operation at #{path} failed to complete in time"
    else
      res = send_req dom, path, Net::HTTP::Get, token
      if JSON.parse(res.body)["done"] == true
        break
      end
      sleep 1
    end
  end
end