require 'plist' require 'terminal-table' module Fastlane module Actions module SharedValues VERIFY_BUILD_CUSTOM_VALUE = :VERIFY_BUILD_CUSTOM_VALUE end class VerifyBuildAction < Action def self.run(params) Dir.mktmpdir do |dir| app_path = self.app_path(params, dir) values = self.gather_cert_info(app_path) values = self.update_with_profile_info(app_path, values) self.print_values(values) self.evaulate(params, values) end end def self.app_path(params, dir) ipa_path = params[:ipa_path] || Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] || '' UI.user_error!("Unable to find ipa file '#{ipa_path}'.") unless File.exist?(ipa_path) ipa_path = File.expand_path(ipa_path) `unzip '#{ipa_path}' -d #{dir}` UI.user_error!("Unable to unzip ipa") unless $? == 0 app_path = File.expand_path("#{dir}/Payload/*.app") app_path end def self.gather_cert_info(app_path) cert_info = `codesign -vv -d #{app_path} 2>&1` UI.user_error!("Unable to verify code signing") unless $? == 0 values = {} parts = cert_info.strip.split(/\r?\n/) parts.each do |part| if part =~ /\AAuthority=i(Phone|OS)/ type = part.split('=')[1].split(':')[0] values['provisioning_type'] = type.downcase =~ /distribution/i ? "distribution" : "development" end if part.start_with? "TeamIdentifier" values['team_identifier'] = part.split('=')[1] end if part.start_with? "Identifier" values['bundle_identifier'] = part.split('=')[1] end end values end def self.update_with_profile_info(app_path, values) profile = `cat #{app_path}/embedded.mobileprovision | security cms -D` UI.user_error!("Unable to extract profile") unless $? == 0 plist = Plist.parse_xml(profile) values['app_name'] = plist['AppIDName'] values['provisioning_uuid'] = plist['UUID'] values['team_name'] = plist['TeamName'] application_identifier_prefix = plist['ApplicationIdentifierPrefix'][0] full_bundle_identifier = "#{application_identifier_prefix}.#{values['bundle_identifier']}" UI.user_error!("Inconsistent identifier found; #{plist['Entitlements']['application-identifier']}, found in the embedded.mobileprovision file, should match #{full_bundle_identifier}, which is embedded in the codesign identity") unless plist['Entitlements']['application-identifier'] == full_bundle_identifier UI.user_error!("Inconsistent identifier found") unless plist['Entitlements']['com.apple.developer.team-identifier'] == values['team_identifier'] values end def self.print_values(values) table = Terminal::Table.new do |t| t.title = 'Summary for VERIFY_BUILD'.green t.headings = 'Key', 'Value' values.each do |key, value| columns = [] columns << key columns << case value when Hash value.map { |k, v| "#{k}: #{v}" }.join("\n") when Array value.join("\n") else value.to_s end t << columns end end puts "" puts table puts "" end def self.evaulate(params, values) if params[:provisioning_type] UI.user_error!("Mismatched provisioning_type. Required: '#{params[:provisioning_type]}''; Found: '#{values['provisioning_type']}'") unless params[:provisioning_type] == values['provisioning_type'] end if params[:provisioning_uuid] UI.user_error!("Mismatched provisioning_uuid. Required: '#{params[:provisioning_uuid]}'; Found: '#{values['provisioning_uuid']}'") unless params[:provisioning_uuid] == values['provisioning_uuid'] end if params[:team_identifier] UI.user_error!("Mismatched team_identifier. Required: '#{params[:team_identifier]}'; Found: '#{values['team_identifier']}'") unless params[:team_identifier] == values['team_identifier'] end if params[:team_name] UI.user_error!("Mismatched team_name. Required: '#{params[:team_name]}'; Found: 'values['team_name']'") unless params[:team_name] == values['team_name'] end if params[:app_name] UI.user_error!("Mismatched app_name. Required: '#{params[:app_name]}'; Found: '#{values['app_name']}'") unless params[:app_name] == values['app_name'] end if params[:bundle_identifier] UI.user_error!("Mismatched bundle_identifier. Required: '#{params[:bundle_identifier]}'; Found: '#{values['bundle_identifier']}'") unless params[:bundle_identifier] == values['bundle_identifier'] end UI.success "Build is verified, have a 🍪." end ##################################################### # @!group Documentation ##################################################### def self.description "Able to verify various settings in ipa file" end def self.details end def self.available_options [ FastlaneCore::ConfigItem.new(key: :provisioning_type, env_name: "FL_VERIFY_BUILD_PROVISIONING_TYPE", description: "Required type of provisioning", optional: true, verify_block: proc do |value| av = %w(distribution development) UI.user_error!("Unsupported provisioning_type, must be: #{av}") unless av.include?(value) end), FastlaneCore::ConfigItem.new(key: :provisioning_uuid, env_name: "FL_VERIFY_BUILD_PROVISIONING_UUID", description: "Required UUID of provisioning profile", optional: true), FastlaneCore::ConfigItem.new(key: :team_identifier, env_name: "FL_VERIFY_BUILD_TEAM_IDENTIFIER", description: "Required team identifier", optional: true), FastlaneCore::ConfigItem.new(key: :team_name, env_name: "FL_VERIFY_BUILD_TEAM_NAME", description: "Required team name", optional: true), FastlaneCore::ConfigItem.new(key: :app_name, env_name: "FL_VERIFY_BUILD_APP_NAME", description: "Required app name", optional: true), FastlaneCore::ConfigItem.new(key: :bundle_identifier, env_name: "FL_VERIFY_BUILD_BUNDLE_IDENTIFIER", description: "Required bundle identifier", optional: true), FastlaneCore::ConfigItem.new(key: :ipa_path, env_name: "FL_VERIFY_BUILD_IPA_PATH", description: "Explicitly set the ipa path", optional: true) ] end def self.output end def self.return_value end def self.authors ["CodeReaper"] end def self.is_supported?(platform) platform == :ios end end end end