diff --git a/lib/fog/aws/iam.rb b/lib/fog/aws/iam.rb index 36c3b65e8..af5e22166 100644 --- a/lib/fog/aws/iam.rb +++ b/lib/fog/aws/iam.rb @@ -85,14 +85,16 @@ module Fog request :upload_signing_certificate model_path 'fog/aws/models/iam' - model :user - collection :users - model :policy - collection :policies model :access_key collection :access_keys + model :group + collection :groups + model :policy + collection :policies model :role collection :roles + model :user + collection :users class Mock def self.data diff --git a/lib/fog/aws/models/iam/group.rb b/lib/fog/aws/models/iam/group.rb new file mode 100644 index 000000000..86eb99a66 --- /dev/null +++ b/lib/fog/aws/models/iam/group.rb @@ -0,0 +1,78 @@ +module Fog + module AWS + class IAM + class Group < Fog::Model + + identity :id, :aliases => 'GroupId' + + attribute :arn, :aliases => 'Arn' + attribute :name, :aliases => 'GroupName' + attribute :path, :aliases => 'Path' + attribute :users, :aliases => 'Users', :type => :array + + def add_user(user_or_name) + requires :name + + user = if user_or_name.is_a?(Fog::AWS::IAM::User) + user_or_name + else + service.users.new(:id => user_or_name) + end + + service.add_user_to_group(self.name, user.identity) + merge_attributes(:users => self.users + [user]) + end + + def attach(policy_arn) + requires :name + + service.attach_group_policy(self.name, policy_arn) + end + + def save + if !persisted? + requires :name + + merge_attributes( + service.create_group(self.name, self.path).body["Group"] + ) + else + params = {} + + if self.name + params['NewGroupName'] = self.name + end + + if self.path + params['NewPath'] = self.path + end + + service.update_group(self.name, params) + true + end + end + + def policies + requires :name + + service.policies(:group_name => self.name) + end + + def reload + requires :name + + data = begin + collection.get(self.name) + rescue Excon::Errors::SocketError + nil + end + + return unless data + + merge_attributes(data.attributes) + self + end + end + end + end +end diff --git a/lib/fog/aws/models/iam/groups.rb b/lib/fog/aws/models/iam/groups.rb new file mode 100644 index 000000000..1920e0467 --- /dev/null +++ b/lib/fog/aws/models/iam/groups.rb @@ -0,0 +1,59 @@ +require 'fog/aws/models/iam/group' + +module Fog + module AWS + class IAM + class Groups < Fog::Collection + + attribute :truncated, :aliases => 'IsTruncated', :type => :boolean + attribute :marker, :aliases => 'Marker' + attribute :username + + model Fog::AWS::IAM::Group + + def all(options = {}) + merge_attributes(options) + + data, records = if self.username + response = service.list_groups_for_user(self.username, options) + [response.body, response.body['GroupsForUser']] + else + response = service.list_groups(options) + [response.body, response.body['Groups']] + end + + merge_attributes(data) + load(records) + end + + def get(identity) + data = service.get_group(identity) + + group = data.body['Group'] + users = data.body['Users'].map { |u| service.users.new(u) } + + new(group.merge(:users => users)) + rescue Fog::AWS::IAM::NotFound + nil + end + + def each + if !block_given? + self + else + subset = dup.all + + subset.each { |f| yield f } + + while subset.truncated + subset = subset.all('Marker' => subset.marker, 'MaxItems' => 1000) + subset.each { |f| yield f } + end + + self + end + end + end + end + end +end diff --git a/lib/fog/aws/models/iam/policies.rb b/lib/fog/aws/models/iam/policies.rb index f8a516e28..03fb2d748 100644 --- a/lib/fog/aws/models/iam/policies.rb +++ b/lib/fog/aws/models/iam/policies.rb @@ -7,29 +7,57 @@ module Fog model Fog::AWS::IAM::Policy attribute :username + attribute :group_name def all - requires :username - # AWS method get_user_policy only returns an array of policy names, this is kind of useless, - # that's why it has to loop through the list to get the details of each element. I don't like it because it makes this method slow - policy_names = service.list_user_policies(self.username).body['PolicyNames'] # it returns an array - policies = policy_names.map do |policy_name| - service.get_user_policy(policy_name, self.username).body['Policy'] - end + requires_one :username, :group_name + + policies = if self.username + all_by_user(self.username) + else + all_by_group(self.group_name) + end + load(policies) # data is an array of attribute hashes end def get(identity) - requires :username + requires_one :username, :group_name - data = service.get_user_policy(identity, self.username).body['Policy'] - new(data) # data is an attribute hash + data = if self.username + service.get_user_policy(identity, self.username) + else + service.get_group_policy(identity, self.group_name) + end.body['Policy'] + + new(data) rescue Fog::AWS::IAM::NotFound nil end def new(attributes = {}) - super({ :username => self.username }.merge!(attributes)) + super(self.attributes.merge(attributes)) + end + + private + + # AWS method get_user_policy and list_group_policies only returns an array of policy names, this is kind of useless, + # that's why it has to loop through the list to get the details of each element. I don't like it because it makes this method slow + + def all_by_group(group_name) + response = service.list_group_policies(group_name) + + response.body['PolicyNames'].map do |policy_name| + service.get_group_policy(policy_name, group_name).body['Policy'] + end + end + + def all_by_user(username) + response = service.list_user_policies(username) + + response.body['PolicyNames'].map do |policy_name| + service.get_user_policy(policy_name, username).body['Policy'] + end end end end diff --git a/lib/fog/aws/models/iam/policy.rb b/lib/fog/aws/models/iam/policy.rb index 9db765847..513ac4ada 100644 --- a/lib/fog/aws/models/iam/policy.rb +++ b/lib/fog/aws/models/iam/policy.rb @@ -6,12 +6,19 @@ module Fog attribute :username, :aliases => 'UserName' attribute :document, :aliases => 'PolicyDocument' + attr_accessor :group_name + def save requires :id - requires :username + requires_one :username, :group_name requires :document - data = service.put_user_policy(username, id, document).body + data = if username + service.put_user_policy(username, id, document).body + else + service.put_group_policy(group_name, id, document).body + end + merge_attributes(data) true end diff --git a/lib/fog/aws/models/iam/user.rb b/lib/fog/aws/models/iam/user.rb index 470a5e33e..c5c967f0d 100644 --- a/lib/fog/aws/models/iam/user.rb +++ b/lib/fog/aws/models/iam/user.rb @@ -3,33 +3,41 @@ module Fog class IAM class User < Fog::Model identity :id, :aliases => 'UserName' - attribute :path, :aliases => 'Path' - attribute :arn, :aliases => 'Arn' - attribute :user_id, :aliases => 'UserId' + + attribute :path, :aliases => 'Path' + attribute :arn, :aliases => 'Arn' + attribute :user_id, :aliases => 'UserId' attribute :created_at, :aliases => 'CreateDate', :type => :time + def access_keys + requires :id + + service.access_keys(:username => id) + end + + def destroy + requires :id + + service.delete_user(id) + true + end + + def groups + service.groups(:username => self.identity) + end + + def policies + requires :id + + service.policies(:username => id) + end + def save requires :id data = service.create_user(id, path || '/').body['User'] merge_attributes(data) true end - - def destroy - requires :id - service.delete_user(id) - true - end - - def policies - requires :id - service.policies(:username => id) - end - - def access_keys - requires :id - service.access_keys(:username => id) - end end end end diff --git a/lib/fog/aws/parsers/iam/list_managed_policies.rb b/lib/fog/aws/parsers/iam/list_managed_policies.rb index 92583c559..a39f4b04a 100644 --- a/lib/fog/aws/parsers/iam/list_managed_policies.rb +++ b/lib/fog/aws/parsers/iam/list_managed_policies.rb @@ -1,8 +1,9 @@ +require 'fog/aws/parsers/iam/policy_parser' + module Fog module Parsers module AWS module IAM - require 'fog/aws/parsers/iam/policy_parser' class ListManagedPolicies < Fog::Parsers::AWS::IAM::PolicyParser def reset super diff --git a/lib/fog/aws/parsers/iam/policy_parser.rb b/lib/fog/aws/parsers/iam/policy_parser.rb index 5af18f972..f19241040 100644 --- a/lib/fog/aws/parsers/iam/policy_parser.rb +++ b/lib/fog/aws/parsers/iam/policy_parser.rb @@ -13,10 +13,10 @@ module Fog when 'Policies' @stack << name when 'Policy' - @role =fresh_policy + @policy = fresh_policy when 'member' if @stack.last == 'Policies' - @role = fresh_policy + @policy = fresh_policy end end super diff --git a/lib/fog/aws/requests/iam/add_user_to_group.rb b/lib/fog/aws/requests/iam/add_user_to_group.rb index 66a58f026..9ad0ba622 100644 --- a/lib/fog/aws/requests/iam/add_user_to_group.rb +++ b/lib/fog/aws/requests/iam/add_user_to_group.rb @@ -30,23 +30,22 @@ module Fog class Mock def add_user_to_group(group_name, user_name) - if data[:groups].key? group_name - if data[:users].key? user_name - - unless data[:groups][group_name][:members].include?(user_name) - data[:groups][group_name][:members] << user_name - end - - Excon::Response.new.tap do |response| - response.status = 200 - response.body = { 'RequestId' => Fog::AWS::Mock.request_id } - end - else - raise Fog::AWS::IAM::NotFound.new("The user with name #{user_name} cannot be found.") - end - else + unless data[:groups].key?(group_name) raise Fog::AWS::IAM::NotFound.new("The group with name #{group_name} cannot be found.") end + + unless data[:users].key?(user_name) + raise Fog::AWS::IAM::NotFound.new("The user with name #{user_name} cannot be found.") + end + + unless data[:groups][group_name][:members].include?(user_name) + data[:groups][group_name][:members] << user_name + end + + Excon::Response.new.tap do |response| + response.status = 200 + response.body = { 'RequestId' => Fog::AWS::Mock.request_id } + end end end end diff --git a/lib/fog/aws/requests/iam/attach_group_policy.rb b/lib/fog/aws/requests/iam/attach_group_policy.rb index e57b94964..0b872d8f9 100644 --- a/lib/fog/aws/requests/iam/attach_group_policy.rb +++ b/lib/fog/aws/requests/iam/attach_group_policy.rb @@ -20,10 +20,10 @@ module Fog # def attach_group_policy(group_name, policy_arn) request( - 'Action' => 'AttachGroupPolicy', - 'GroupName' => group_name, - 'PolicyArn' => policy_arn, - :parser => Fog::Parsers::AWS::IAM::Basic.new + 'Action' => 'AttachGroupPolicy', + 'GroupName' => group_name, + 'PolicyArn' => policy_arn, + :parser => Fog::Parsers::AWS::IAM::Basic.new ) end end diff --git a/lib/fog/aws/requests/iam/get_group.rb b/lib/fog/aws/requests/iam/get_group.rb index c62a8c79a..3fcb15adb 100644 --- a/lib/fog/aws/requests/iam/get_group.rb +++ b/lib/fog/aws/requests/iam/get_group.rb @@ -45,9 +45,9 @@ module Fog Excon::Response.new.tap do |response| response.body = { 'Group' => { 'GroupId' => data[:groups][group_name][:group_id], - 'Path' => data[:groups][group_name][:path], + 'Path' => data[:groups][group_name][:path], 'GroupName' => group_name, - 'Arn' => (data[:groups][group_name][:arn]).strip + 'Arn' => (data[:groups][group_name][:arn]).strip }, 'Users' => data[:groups][group_name][:members].map { |user| get_user(user).body['User'] }, 'RequestId' => Fog::AWS::Mock.request_id } diff --git a/lib/fog/aws/requests/iam/get_group_policy.rb b/lib/fog/aws/requests/iam/get_group_policy.rb index 4dec0a150..4acb05e1b 100644 --- a/lib/fog/aws/requests/iam/get_group_policy.rb +++ b/lib/fog/aws/requests/iam/get_group_policy.rb @@ -22,10 +22,10 @@ module Fog # def get_group_policy(policy_name, group_name) request({ - 'Action' => 'GetGroupPolicy', - 'PolicyName' => policy_name, - 'GroupName' => group_name, - :parser => Fog::Parsers::AWS::IAM::GetGroupPolicy.new + 'Action' => 'GetGroupPolicy', + 'PolicyName' => policy_name, + 'GroupName' => group_name, + :parser => Fog::Parsers::AWS::IAM::GetGroupPolicy.new }) end end diff --git a/lib/fog/aws/requests/iam/list_group_policies.rb b/lib/fog/aws/requests/iam/list_group_policies.rb index f83adc924..66168c62d 100644 --- a/lib/fog/aws/requests/iam/list_group_policies.rb +++ b/lib/fog/aws/requests/iam/list_group_policies.rb @@ -32,6 +32,22 @@ module Fog }.merge!(options)) end end + + class Mock + def list_group_policies(group_name, options = {}) + #FIXME: doesn't use options atm + if data[:groups].key? group_name + Excon::Response.new.tap do |response| + response.body = { 'PolicyNames' => data[:groups][group_name][:policies].keys, + 'IsTruncated' => false, + 'RequestId' => Fog::AWS::Mock.request_id } + response.status = 200 + end + else + raise Fog::AWS::IAM::NotFound.new("The user with name #{user_name} cannot be found.") + end + end + end end end end diff --git a/lib/fog/aws/requests/iam/update_group.rb b/lib/fog/aws/requests/iam/update_group.rb index 79dd6b8c8..3ac939c92 100644 --- a/lib/fog/aws/requests/iam/update_group.rb +++ b/lib/fog/aws/requests/iam/update_group.rb @@ -26,12 +26,49 @@ module Fog # def update_group(group_name, options = {}) request({ - 'Action' => 'UpdateGroup', - 'GroupName' => group_name, - :parser => Fog::Parsers::AWS::IAM::UpdateGroup.new + 'Action' => 'UpdateGroup', + 'GroupName' => group_name, + :parser => Fog::Parsers::AWS::IAM::UpdateGroup.new }.merge!(options)) end end + + class Mock + def update_group(group_name, options = {}) + raise Fog::AWS::IAM::NotFound.new( + "The user with name #{group_name} cannot be found." + ) unless self.data[:groups].key?(group_name) + + response = Excon::Response.new + + group = self.data[:groups][group_name] + + new_path = options['NewPath'] + new_group_name = options['NewGroupName'] + + if new_path + unless new_path.match(/\A\/[a-zA-Z0-9]+\/\Z/) + raise Fog::AWS::IAM::ValidationError, + "The specified value for path is invalid. It must begin and end with / and contain only alphanumeric characters and/or / characters." + end + + group[:path] = new_path + end + + if new_group_name + self.data[:groups].delete(group_name) + self.data[:groups][new_group_name] = group + end + + response.status = 200 + response.body = { + 'Group' => {}, + 'RequestId' => Fog::AWS::Mock.request_id + } + + response + end + end end end end diff --git a/tests/models/iam/groups_tests.rb b/tests/models/iam/groups_tests.rb new file mode 100644 index 000000000..4eafe7f3d --- /dev/null +++ b/tests/models/iam/groups_tests.rb @@ -0,0 +1,59 @@ +Shindo.tests("Fog::Compute[:iam] | groups", ['aws','iam']) do + + service = Fog::AWS[:iam] + group_name = uniq_id('fog-test-group') + policy_name = uniq_id('fog-test-policy') + group = nil + document = {"Statement" => [{"Effect" => "Allow", "Action" => "*", "Resource" => "*"}]} + + tests('#create').succeeds do + group = service.groups.create(:name => group_name) + + group.name == group_name + end + + tests('#all').succeeds do + service.groups.all.map(&:name).include?(group_name) + end + + tests('update').succeeds do + new_path = group.path = "/newpath/" + group.save + + group.reload.path == new_path + end + + tests('group') do + policy = nil + + tests('#policies', '#create') do + policy = group.policies.create(:id => policy_name, :document => document) + end + + tests('#policies', '#get').succeeds do + group.policies.get(policy_name) != nil + end + + tests('#policies', '#all').succeeds do + group.policies.all.map(&:id).include?(policy.id) + end + + tests('#users', 'when none').succeeds do + group.users.empty? + end + + user = nil + + tests('#add_user').succeeds do + user = service.users.create(:id => 'fog-test') + + group.add_user(user) + + group.users.include?(user) + end + + tests('#users').succeeds do + group.reload.users.map(&:identity).include?(user.identity) + end + end +end