Android App Development: Securing Content Providers
content://authority/path/id
, where authority
refers to the Content Provider itself, and path/id
to data stored within the Content Provider. To access data stored within a Content Provider an app must use the query()
, insert()
, update()
, and delete()
methods provided by a ContentResolver
object. These methods provide synchronous RPC like access to the Content Provider. Access to the Content Provider through these methods can be controlled using permissions, however as we shall see, Content Providers allow much finer access control than Activities and Services. The different mechanisms for applying permissions that we cover below have different scopes, from those that apply to the whole of the Content Provider, through to those that only apply to individual data items. If two permissions apply to the same content URI, then the one with the
smaller scope, i.e. the more fine-grained permission, is the one that takes precedence.
Basic access control
The most basic level of access control is performed in much the same way as for Activities and Services by using theandroid:permission
attribute of the <provider>
element in the app’s AndroidManifest.xml
file. This defines a permission that controls both read and write access to the whole of the Content provider. However, following the Principle of Least Privilege, we can apply separate read and write permissions. Sometimes we would like other apps to be able to read the contents of a Content Provider but not change them, and sometimes we want to allow an app to add a new entry to our database, but not to be able to read what is already in there. For example a logging system where apps can record what they are doing, but cannot read what other apps are doing. This means that having write permission does not imply having read permission. This is a common misconception. Read permission is controlled by the android:readPermission
attribute of the <provider>
element, and write permission by the android:writePermission
attribute. The access methods of a Content Provider are controlled as follows: -
query()
requires read permission, and
-
-
insert()
,update()
, anddelete()
require write permission.
-
Controlling access to subsets of the data
To further apply the Principle of Least Privilege we can give separate permissions to subsets of a Content Provider’s data. This will override any global permissions set on the whole of the Content Provider. We can do this by adding<path-permission>
elements to the <provider>
element. There are three different attributes that can be set to control which subset of the data the permissions apply to: android:path="/subpath"
/subpath
, but not to subdirectories of /subpath
, android:pathPrefix="/subpath"
/subpath
, and android:pathPattern
content://authority/subpath1
and content://authority/subpath2
we would add the following: <provider …>…<path-permission android:pathPrefix="/subpath1"android:readPermission="com.example.myamazingapp.SUBPATH1_READ_PERMISSION"android:writePermission="com.example.myamazingapp.SUBPATH1_WRITE_PERMISSION" /><path-permission android:pathPrefix="/subpath2"android:readPermission="com.example.myamazingapp.SUBPATH2_READ_PERMISSION"android:writePermission="com.example.myamazingapp.SUBPATH2_WRITE_PERMISSION" />
</provider>
content://authority/subpath1
but deny it access to content://authority/subpath2
. Temporary access to specific data items
We can take the Principle of Least Privilege one step further and consider the case where we might want to give an app temporary access to some of the data within a Content Provider. One obvious example of this is the case of an email client that may store emails and attachments in a database accessed via a Content Provider. Now suppose one of the attachments is a media file that the email client does not know how to handle, but for which there is another app on the device that can handle such files. We would like to give that app temporary access to the media file within the Content Provider in order to play the file, but we do not want to allow the app permanent access to the file, nor do we want to give it complete access to all the user’s emails. To handle this situation Android provides URI permissions. URI permissions allow any component that has permission to access a content URI within a Content Provider to temporarily grant another component that permission, even if that component does not itself have permission to access the Content Provider.Enabling URI permissions
By default the granting of temporary URI permissions is disabled. It can be turned on in two different ways. The first way enables any component that has permission to access the Content Provider the ability to grant temporary access to any content URI within the Content Provider to another component. To do this we set the attributeandroid:grantUriPermission="true"
in the <provider>
element. Again following the Principle of Least Privilege we might not want to do this, so the second way is to add <grant-uri-permission>
elements to the <provider>
element. Like with <path-permission>
elements (above), you can set one of three different attributes: android:path
, android:pathPrefix
, and android:pathPattern
. For example, <provider …>…<grant-uri-permission android:path="/subpath" />
</provider>
/subpath
, but not for subdirectories of /subpath
. Granting temporary permissions
So far we have discussed how we can give a component permission to grant temporary permissions to other components, however we have not yet said anything about how to actually grant temporary permissions. This is done programatically, and there are two ways to do this. The first, and preferred way, is through the setting of flags on Intents. The way this works is as follows:- A component wants to ask another component, like an Activity (say an image viewer) or a Service, to perform an action on some data in a Content Provider for which it has access.
- As usual, the component will create an Intent and call
startActivity()
orstartService()
with that Intent.
- As usual, the component will create an Intent and call
- To tell the receiving component what data it should perform the action on the sending component calls the
setData()
method with the required content URI.
- To tell the receiving component what data it should perform the action on the sending component calls the
- In order to give the component that receives the Intent permission to access the content URI where the data is stored, the sending component uses the
setFlags()
method to set either theFLAG_GRANT_READ_URI_PERMISSION
orFLAG_GRANT_WRITE_URI_PERMISSION
flag on the Intent before sending it.
- In order to give the component that receives the Intent permission to access the content URI where the data is stored, the sending component uses the
- When
startActivity()
(orstartService()
etc.) is called, the system will check whether the calling component has permission to grant the temporary access to the content URI, if it does, the Intent will be delivered to the receiving component.
- When
grantUriPermission()
method, but this is not recommended. Finally, the revokeUriPermission()
method can be used to revoke temporary permissions, and must be called if a content URI for which a temporary permission is granted, is removed from the Content Provider.Our purpose is to transform access to education.
We offer a diverse selection of courses from leading universities and cultural institutions from around the world. These are delivered one step at a time, and are accessible on mobile, tablet and desktop, so you can fit learning around your life.
We believe learning should be an enjoyable, social experience, so our courses offer the opportunity to discuss what you’re learning with others as you go, helping you make fresh discoveries and form new ideas.
You can unlock new opportunities with unlimited access to hundreds of online short courses for a year by subscribing to our Unlimited package. Build your knowledge with top universities and organisations.
Learn more about how FutureLearn is transforming access to education