cocoapods-entitlements-statistics 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b18c67b431404139e32390800cee36de2c9443a053716f29832d32d22424954a
4
+ data.tar.gz: be771fb8171db38589b66f32155b946958d5d34b79b4ace92f8e8e6ea146bbd5
5
+ SHA512:
6
+ metadata.gz: e3596496a3b67f9ae0cb268fe08118f415ca5e9073cea82017bb8176cef932158b44768cfbcebb9b7185b74ff293420895f2221c8aa02d069ebae6dc04305c70
7
+ data.tar.gz: 9c740e1c280ab955be62a9f51a7678bc07a50c5a28d16049cedf1964283f17ec0c12d87a6cab6710c0540821f6da7a888130264b3e7ca0d72b63a2288951f1a1
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ pkg
3
+ .idea/
data/Gemfile ADDED
@@ -0,0 +1,18 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cocoapods-entitlements-statistics.gemspec
4
+ gemspec
5
+ gem 'crimp'
6
+ gem 'zip'
7
+
8
+ group :development do
9
+ gem 'cocoapods'
10
+
11
+ gem 'mocha'
12
+ gem 'bacon'
13
+ gem 'mocha-on-bacon'
14
+ gem 'prettybacon'
15
+ gem 'crimp'
16
+ gem 'zip'
17
+
18
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2023 bin <tang.bin@olaola.chat>
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # cocoapods-entitlements-statistics
2
+
3
+ iOS app 权限统计, 使用cocoapods plugin 触发
4
+
5
+
6
+ ## 安装
7
+
8
+ ```shell
9
+ # 下载Releases v0.0.1 gem文件,然后进入下载目录执行下面命令
10
+ sudo gem install cocoapods-entitlements-statistics-0.0.1.gem
11
+ ```
12
+
13
+ ## 使用
14
+
15
+ 在 Podfile 文件添加插件引用
16
+
17
+ ```ruby
18
+ #引入插件, 可以自定义 report_path, 默认在(iOS)项目根目录
19
+ plugin 'cocoapods-entitlements-statistics', :report_path => '/Users/rd01/Desktop'
20
+ #plugin 'cocoapods-entitlements-statistics'
21
+
22
+ target 'xxx' do
23
+ ....
24
+
25
+ ```
26
+
27
+ 执行 pod install 会输出统计报告路径
28
+
29
+ ```shell
30
+ $ pod install
31
+ .....
32
+ [!] Entitlements Statistics Report : path/to/entitlements_statistics/analyze_report
33
+ Pod installation complete! There are 75 dependencies from the Podfile and 116 total pods installed.
34
+
35
+ ```
36
+
37
+ ## 说明
38
+
39
+ ##### 版本权限统计
40
+
41
+ 默认会在 ~/appInfo-#{app_bundle_id}/ 下为每个版本建立权限统计文件
42
+
43
+ ```yaml
44
+
45
+ ├── appInfo-com.ola.chat
46
+ │   ├── entitlements_5.2.0.yml
47
+ │   ├── entitlements_5.3.0.yml
48
+ │   └── entitlements_versions.yml
49
+ ```
50
+
51
+ ##### 报告内容如下:
52
+
53
+ * 权限变化对比分析(增、删、改)
54
+ * (对比的各)版本权限列表详情
55
+
56
+ ```yaml
57
+
58
+ compared 5.3.0 5.2.0
59
+
60
+ modify capabilitys :
61
+ 5.3.0
62
+ - items ..
63
+ 5.2.0
64
+ - items ..
65
+ ------------------------------
66
+ add capabilitys :
67
+ - items ..
68
+ ------------------------------
69
+ remove capabilitys :
70
+ - items ..
71
+ ------------------------------
72
+
73
+ 5.3.0 entitlements list:
74
+ ...
75
+
76
+ 5.2.0 entitlements list:
77
+ ...
78
+
79
+ ```
80
+
81
+ ## iOS 项目权限
82
+
83
+ #### 大致分类 Capabilitys、info.plist(Cocoa Keys)
84
+
85
+ * Capabilitys
86
+
87
+ > Xcode->Target->Signing&Capabilitys->Capabilitys 下添加和删除 Capabilitys , 这里不是随便添加的,需要首先在苹果开发者后台注册或者声明对应Capabilitys. 更新的Provisioning Profile文件,其中包含Entitlements 字段包含(几乎所有的)已注册Capabilitys, Xcode中添加Capabilitys后会同Profile文件校验。不匹配则无法通过编译签名。
88
+
89
+ Supported capabilities (iOS):
90
+ https://developer.apple.com/help/account/reference/supported-capabilities-ios
91
+
92
+ * info.plist (Cocoa Key-Values)
93
+ * NS{Permissions}UsageDescription
94
+ > info.plist 中声明使用相机、麦克风、摄像头..等权限的说明
95
+
96
+ * 其它权限声明Keys
97
+ > info.plist 中也会包含一些权限的详细设置、声明,比如屏幕高刷、代理、后台模式...
98
+
99
+ info.plist 涉及的所有 Cocoa Keys
100
+ https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW1
101
+
102
+ #### 涉及的文件
103
+
104
+ * info.plist
105
+ * Runner.entitlements Capabilitys被添加后的,Xcode自动创建plist格式文件,里面包含相关Capability的声明、设置
106
+ * {embedded/xxx-xx-xx}.mobileprovision
107
+ 包括里面的Entitlements字段, 保存一些 Capabilitys被添加后的 声明、设置
108
+ * Runner.xcodeproj
109
+ 工程文件SystemCapabilities字段包含了一些Capabilitys声明
110
+
111
+ 相关文档:
112
+
113
+ xx.mobileprovision
114
+ https://developer.apple.com/forums/thread/685723
115
+
116
+ Runner.entitlements
117
+ https://developer.apple.com/library/archive/documentation/Miscellaneous/Reference/EntitlementKeyReference/Chapters/AboutEntitlements.html#//apple_ref/doc/uid/TP40011195-CH1-SW1
118
+
119
+ #### 问题
120
+
121
+ * IPA包中找不到 In-App Purchase 功能标记
122
+
123
+ > 一般Capabilitys添加后会在Runner.entitlements、xx.mobileprovision找到对应key:value声明、设置
124
+
125
+ 添加 In-App Purchase后, 仅在Runner.xcodeproj中有 "com.apple.InAppPurchase"=>{"enabled"=>"1"} 声明
126
+
127
+ * Background Modes 属于 Capabilitys 中的异类
128
+
129
+ > 一般Capabilitys添加后会在Runner.entitlements、xx.mobileprovision找到对应key:value声明、设置, 而Background Modes在这里没有任何记录
130
+
131
+ Background Modes的声明位置:
132
+ * Runner.xcodeproj : "com.apple.BackgroundModes"=>{"enabled"=>"1"}
133
+ * info.plist : "UIBackgroundModes"=>{"audio"、"remote-notification" ...}
134
+
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ def specs(dir)
4
+ FileList["spec/#{dir}/*_spec.rb"].shuffle.join(' ')
5
+ end
6
+
7
+ desc 'Runs all the specs'
8
+ task :specs do
9
+ sh "bundle exec bacon #{specs('**')}"
10
+ end
11
+
12
+ task :default => :specs
13
+
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'cocoapods-entitlements-statistics/gem_version.rb'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'cocoapods-entitlements-statistics'
8
+ spec.version = CocoapodsEntitlementsStatistics::VERSION
9
+ spec.authors = ['bin']
10
+ spec.email = ['tang.bin@olaola.chat']
11
+ spec.description = %q{A short description of cocoapods-entitlements-statistics.}
12
+ spec.summary = %q{A longer description of cocoapods-entitlements-statistics.}
13
+ spec.homepage = 'https://github.com/EXAMPLE/cocoapods-entitlements-statistics'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_runtime_dependency "yaml"
22
+ spec.add_runtime_dependency "crimp"
23
+ spec.add_dependency 'CFPropertyList', '< 3.1.0', '>= 2.3.4'
24
+ spec.add_development_dependency 'bundler', '>= 1.12'
25
+ spec.add_dependency 'rubyzip', '>= 1.2', '< 3.0'
26
+ spec.add_development_dependency 'rake'
27
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "helper"
4
+ require 'fileutils'
5
+ require 'yaml'
6
+
7
+ module AppEntitlementsStatistics
8
+
9
+ class Analyzer
10
+ include Helper::Utils
11
+
12
+ attr_reader :store_path
13
+ attr_reader :report_path
14
+ attr_reader :identifier
15
+
16
+ def initialize(identifier,report_path: nil, store_path: nil)
17
+ @identifier = identifier
18
+ @report_path = report_path
19
+ @store_path = store_path
20
+ end
21
+
22
+ def analyze
23
+ load_version
24
+ return singleGenerate unless @versions.length > 1
25
+ pre_version = @versions[1]
26
+ cur_version = @versions[0]
27
+ pre_entitlements = load_entitlements_yaml(pre_version)
28
+ cur_entitlements = load_entitlements_yaml(cur_version)
29
+ return "" unless pre_entitlements.any?
30
+ return "" unless cur_entitlements.any?
31
+
32
+ capabilities_diff = analyze_capabilities_diff(pre_entitlements,cur_entitlements)
33
+ usage_descs_diff = analyze_usage_descs_diff(pre_entitlements,cur_entitlements);
34
+ report_path = generate(capabilities_diff,usage_descs_diff)
35
+ report_path
36
+ end
37
+
38
+ def singleGenerate
39
+ return "" unless @versions.length > 0
40
+ output = "no more versions to compare !! "
41
+ output += "\n\n\n#{@versions.first} entitlements list: "
42
+ output += "\n" + read_each_line(@versions.first)
43
+ output += "\n" + "----------------------------------------"
44
+ report_path = report_file_name(path: @report_path)
45
+ File.open(report_path, 'w') { |file|
46
+ file.write(output)
47
+ }
48
+ report_path
49
+ end
50
+
51
+ def generate(capabilities_diff,usage_descs_diff)
52
+ return unless @versions.length > 1
53
+ cur_version = @versions[0]
54
+ pre_version = @versions[1]
55
+
56
+ output = "\n" + "compared #{cur_version} #{pre_version}"
57
+ output += "\n" + capabilities_diff unless capabilities_diff.empty?
58
+ output += "\n" + usage_descs_diff unless usage_descs_diff.empty?
59
+ output += "\n\nThere is no difference between the two versions \n\n" unless (!capabilities_diff.empty? && !usage_descs_diff.empty?)
60
+ # puts output
61
+
62
+ output += "\n\n\n#{cur_version} entitlements list: "
63
+ output += "\n" + read_each_line(cur_version)
64
+ output += "\n" + "----------------------------------------"
65
+ output += "\n\n#{pre_version} entitlements list: "
66
+ output += "\n" + read_each_line(pre_version)
67
+ output += "\n" + "----------------------------------------"
68
+
69
+ report_path = report_file_name(path: @report_path)
70
+ File.open(report_path, 'w') { |file|
71
+ file.write(output)
72
+ }
73
+ report_path
74
+ end
75
+
76
+ def read_each_line(version)
77
+ file_name = entitlements_yaml_name(version,@identifier, path: @store_path)
78
+ file_content = ""
79
+ File.open(file_name,"r").each_line do |line|
80
+ file_content += "\n" + line
81
+ end
82
+ file_content
83
+ end
84
+
85
+ def load_version
86
+ yaml_name = versions_yaml_name(@identifier, path: @store_path)
87
+ if File.exist?(yaml_name)
88
+ @versions = YAML.load_file(yaml_name)
89
+ end
90
+ end
91
+
92
+ def load_entitlements_yaml(version)
93
+ yaml_name = entitlements_yaml_name(version,@identifier, path: @store_path)
94
+ yaml_content = [ ]
95
+ if File.exist?(yaml_name)
96
+ yaml_content = YAML.load_file(yaml_name)
97
+ end
98
+ puts "#{yaml_name} not found!!!" unless !yaml_content.empty?
99
+ yaml_content
100
+ end
101
+
102
+ def analyze_capabilities_diff(pre,cur)
103
+ output = ""
104
+ cur_capabilities_summary = cur['Capabilities_Summary']
105
+ pre_capabilities_summary = pre['Capabilities_Summary']
106
+ cur_capabilities = cur['Capabilities']
107
+ pre_capabilities = pre['Capabilities']
108
+ output = ""
109
+ # 修改
110
+ comm_capabilities = cur_capabilities_summary.keys & pre_capabilities_summary.keys
111
+ modifys = modifys_cur = modifys_pre = ""
112
+ comm_capabilities.each do |key|
113
+ if cur_capabilities_summary[key] != pre_capabilities_summary[key]
114
+ modifys_cur += "\n" + '- ' + key + ': ' + cur_capabilities[key].to_s
115
+ modifys_pre += "\n" + '- ' + key + ': ' + pre_capabilities[key].to_s
116
+ end
117
+ end
118
+
119
+ modifys += "\n" + @versions[0] unless modifys_cur.empty?
120
+ modifys += modifys_cur unless modifys_cur.empty?
121
+ modifys += "\n" + @versions[1] unless modifys_pre.empty?
122
+ modifys += modifys_pre unless modifys_pre.empty?
123
+
124
+ #新增
125
+ add_capabilities = cur_capabilities_summary.keys - pre_capabilities_summary.keys
126
+ adds = ""
127
+ add_capabilities.each do |key|
128
+ adds += "\n" + '- ' + key + ': ' + cur_capabilities[key].to_s
129
+ end
130
+
131
+ #移除
132
+ remove_capabilities = pre_capabilities_summary.keys - cur_capabilities_summary.keys
133
+ removes = ""
134
+ remove_capabilities.each do |key|
135
+ removes += "\n" + '- ' + key + ': ' + pre_capabilities[key].to_s
136
+ end
137
+
138
+ if !modifys.empty?
139
+ output += "\n" + "modify capabilitys : "
140
+ output += modifys
141
+ output += "\n------------------------------"
142
+ end
143
+
144
+ if !adds.empty?
145
+ output += "\n" + "add capabilitys : "
146
+ output += adds
147
+ output += "\n------------------------------"
148
+ end
149
+
150
+ if !removes.empty?
151
+ output += "\n" + "remove capabilitys : "
152
+ output += removes
153
+ output += "\n------------------------------"
154
+ end
155
+
156
+ output
157
+ end
158
+
159
+ def analyze_usage_descs_diff(pre,cur)
160
+ output = ""
161
+ cur_usage_descs_summary = cur['PermissionsUsageDescription_Summary']
162
+ pre_usage_descs_summary = pre['PermissionsUsageDescription_Summary']
163
+
164
+ cur_usage_descs = cur['PermissionsUsageDescription']
165
+ pre_usage_descs = pre['PermissionsUsageDescription']
166
+
167
+ # 修改
168
+ comm_usage_descs = cur_usage_descs_summary.keys & pre_usage_descs_summary.keys
169
+ modifys = modifys_cur = modifys_pre = ""
170
+ comm_usage_descs.each do |key|
171
+ if cur_usage_descs_summary[key] != pre_usage_descs_summary[key]
172
+ modifys_cur += "\n" + '- ' + key + ': ' + cur_usage_descs[key].to_s
173
+ modifys_pre += "\n" + '- ' + key + ': ' + pre_usage_descs[key].to_s
174
+ end
175
+ end
176
+
177
+ modifys += "\n" + @versions[0] unless modifys_cur.empty?
178
+ modifys += modifys_cur unless modifys_cur.empty?
179
+ modifys += "\n" + @versions[1] unless modifys_pre.empty?
180
+ modifys += modifys_pre unless modifys_pre.empty?
181
+
182
+ #新增
183
+ add_usage_descs = cur_usage_descs_summary.keys - pre_usage_descs_summary.keys
184
+ adds = ""
185
+ add_usage_descs.each do |key|
186
+ adds += "\n" + '- ' + key + ': ' + cur_usage_descs[key].to_s
187
+ end
188
+
189
+ #移除
190
+ remove_usage_descs = pre_usage_descs_summary.keys - cur_usage_descs_summary.keys
191
+ removes = ""
192
+ remove_usage_descs.each do |key|
193
+ removes += "\n" + '- ' + key + ': ' + pre_usage_descs[key].to_s
194
+ end
195
+
196
+ if !modifys.empty?
197
+ output += "\n" + "modify permissions usage description : "
198
+ output += modifys
199
+ output += "\n------------------------------"
200
+ end
201
+
202
+ if !adds.empty?
203
+ output += "\n" + "add permissions usage description : "
204
+ output += adds
205
+ output += "\n------------------------------"
206
+ end
207
+
208
+ if !removes.empty?
209
+ output += "\n" + "remove permissions usage description : "
210
+ output += removes
211
+ output += "\n------------------------------"
212
+ end
213
+
214
+ output
215
+ end
216
+ end
217
+
218
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AppEntitlementsStatistics
4
+ module Inflector
5
+ def ai_snakecase
6
+ gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
7
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
8
+ .tr('-', '_')
9
+ .gsub(/\s/, '_')
10
+ .gsub(/__+/, '_')
11
+ .downcase
12
+ end
13
+
14
+ def ai_camelcase(first_letter: :upper, separators: ['-', '_', '\s'])
15
+ str = dup
16
+
17
+ separators.each do |s|
18
+ str = str.gsub(/(?:#{s}+)([a-z])/) { $1.upcase }
19
+ end
20
+
21
+ case first_letter
22
+ when :upper, true
23
+ str = str.gsub(/(\A|\s)([a-z])/) { $1 + $2.upcase }
24
+ when :lower, false
25
+ str = str.gsub(/(\A|\s)([A-Z])/) { $1 + $2.downcase }
26
+ end
27
+
28
+ str
29
+ end
30
+ end
31
+ end
32
+
33
+ class String
34
+ include AppEntitlementsStatistics::Inflector
35
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyrights rails
4
+ # Copy from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb
5
+
6
+ module AppEntitlementsStatistics
7
+ module Tryable # :nodoc:
8
+ ##
9
+ # :method: try
10
+ #
11
+ # :call-seq:
12
+ # try(*a, &b)
13
+ #
14
+ # Invokes the public method whose name goes as first argument just like
15
+ # +public_send+ does, except that if the receiver does not respond to it the
16
+ # call returns +nil+ rather than raising an exception.
17
+ #
18
+ # This method is defined to be able to write
19
+ #
20
+ # @person.try(:name)
21
+ #
22
+ # instead of
23
+ #
24
+ # @person.name if @person
25
+ #
26
+ # +try+ calls can be chained:
27
+ #
28
+ # @person.try(:spouse).try(:name)
29
+ #
30
+ # instead of
31
+ #
32
+ # @person.spouse.name if @person && @person.spouse
33
+ #
34
+ # +try+ will also return +nil+ if the receiver does not respond to the method:
35
+ #
36
+ # @person.try(:non_existing_method) # => nil
37
+ #
38
+ # instead of
39
+ #
40
+ # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
41
+ #
42
+ # +try+ returns +nil+ when called on +nil+ regardless of whether it responds
43
+ # to the method:
44
+ #
45
+ # nil.try(:to_i) # => nil, rather than 0
46
+ #
47
+ # Arguments and blocks are forwarded to the method if invoked:
48
+ #
49
+ # @posts.try(:each_slice, 2) do |a, b|
50
+ # ...
51
+ # end
52
+ #
53
+ # The number of arguments in the signature must match. If the object responds
54
+ # to the method the call is attempted and +ArgumentError+ is still raised
55
+ # in case of argument mismatch.
56
+ #
57
+ # If +try+ is called without arguments it yields the receiver to a given
58
+ # block unless it is +nil+:
59
+ #
60
+ # @person.try do |p|
61
+ # ...
62
+ # end
63
+ #
64
+ # You can also call try with a block without accepting an argument, and the block
65
+ # will be instance_eval'ed instead:
66
+ #
67
+ # @person.try { upcase.truncate(50) }
68
+ #
69
+ # Please also note that +try+ is defined on +Object+. Therefore, it won't work
70
+ # with instances of classes that do not have +Object+ among their ancestors,
71
+ # like direct subclasses of +BasicObject+.
72
+ def try(method_name = nil, *args, &block)
73
+ if method_name.nil? && block_given?
74
+ if block.arity.zero?
75
+ instance_eval(&b)
76
+ else
77
+ yield self
78
+ end
79
+ elsif respond_to?(method_name)
80
+ public_send(method_name, *args, &block)
81
+ end
82
+ end
83
+
84
+ ##
85
+ # :method: try!
86
+ #
87
+ # :call-seq:
88
+ # try!(*a, &b)
89
+ #
90
+ # Same as #try, but raises a +NoMethodError+ exception if the receiver is
91
+ # not +nil+ and does not implement the tried method.
92
+ #
93
+ # "a".try!(:upcase) # => "A"
94
+ # nil.try!(:upcase) # => nil
95
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
96
+ def try!(method_name = nil, *args, &block)
97
+ if method_name.nil? && block_given?
98
+ if block.arity.zero?
99
+ instance_eval(&block)
100
+ else
101
+ yield self
102
+ end
103
+ else
104
+ public_send(method_name, *args, &block)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ class Object
111
+ include AppEntitlementsStatistics::Tryable
112
+ end