Repository: ansible/awx
Pull request: disallow changing token-app
##### SUMMARY
Fixes: https://github.com/ansible/awx/issues/1376

Blocks changing the application field on a token via PUT or PATCH as this field should be immutable.  

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->

 - Bugfix Pull Request


##### COMPONENT NAME
<!--- Name of the module/plugin/module/task -->
 - API


##### AWX VERSION
<!--- Paste verbatim output from `make VERSION` between quotes below -->
```
AWX version: 1.0.4-73
AWX install method: docker for mac
Ansible version: N/A
Operating System:
Web Browser:
```


##### ADDITIONAL INFORMATION



Problem statement:
Able to PATCH a token to make its application None
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
 - API

##### SUMMARY
Able to remove application from token

##### ENVIRONMENT
* AWX version: 1.0.4-73
* AWX install method: docker for mac
* Ansible version:  N/A
* Operating System:
* Web Browser:

##### STEPS TO REPRODUCE

In the API browser go to something like `/api/v2/tokens/1/`

PATCH

```json
{
    "application": null,
}
```

##### EXPECTED RESULTS

400, from acceptance docs

##### ACTUAL RESULTS

changed.

##### ADDITIONAL INFORMATION

PATCH is not listed in the OPTIONS, which I guess is another bug.

Hints from issue discussion:

Commit message: add serializer for token detail

Patch to review:
diff --git a/awx/api/serializers.py b/awx/api/serializers.py
index 0057c69bd95a..1e8fdc8f12d0 100644
--- a/awx/api/serializers.py
+++ b/awx/api/serializers.py
@@ -939,7 +939,6 @@ class Meta:
             'expires', 'scope', 'application',
         )
         read_only_fields = ('user', 'token', 'expires')
-        read_only_on_update_fields = ('application',)
         
     def get_token(self, obj):
         request = self.context.get('request', None)
@@ -1105,6 +1104,20 @@ def create(self, validated_data):
             )
         return obj
         
+        
+class OAuth2TokenDetailSerializer(OAuth2TokenSerializer):
+
+    refresh_token = serializers.SerializerMethodField()
+    token = serializers.SerializerMethodField()
+
+    class Meta:
+        model = OAuth2AccessToken
+        fields = (
+            '*', '-name', 'description', 'user', 'token', 'refresh_token',
+            'application', 'expires', 'scope',
+        )
+        read_only_fields = ('user', 'token', 'expires', 'application')       
+
 
 class OAuth2AuthorizedTokenSerializer(BaseSerializer):
     
@@ -1118,7 +1131,6 @@ class Meta:
             'expires', 'scope', 'application',
         )
         read_only_fields = ('user', 'token', 'expires')
-        read_only_on_update_fields = ('application',)
         
     def get_token(self, obj):
         request = self.context.get('request', None)
@@ -1171,8 +1183,7 @@ class Meta:
             '*', '-name', 'description', 'user', 'token', 'refresh_token',
             'application', 'expires', 'scope',
         )
-        read_only_fields = ('user', 'token', 'expires')
-        read_only_on_update_fields = ('application',)
+        read_only_fields = ('user', 'token', 'expires', 'application')
 
     def get_modified(self, obj):
         if obj is None:
diff --git a/awx/api/views.py b/awx/api/views.py
index a5f4a662b334..137df4d949da 100644
--- a/awx/api/views.py
+++ b/awx/api/views.py
@@ -1587,7 +1587,7 @@ class OAuth2TokenDetail(RetrieveUpdateDestroyAPIView):
     view_name = _("OAuth Token Detail")
 
     model = OAuth2AccessToken
-    serializer_class = OAuth2TokenSerializer
+    serializer_class = OAuth2TokenDetailSerializer
 
 
 class OAuth2TokenActivityStreamList(ActivityStreamEnforcementMixin, SubListAPIView):


Code review comments:
diff_hunk: @@ -1105,6 +1104,20 @@ def create(self, validated_data):
             )
         return obj
         
+        
+class OAuth2TokenDetailSerializer(OAuth2TokenSerializer):
+
+    refresh_token = serializers.SerializerMethodField()
+    token = serializers.SerializerMethodField()
+
+    class Meta:
+        model = OAuth2AccessToken
+        fields = (
+            '*', '-name', 'description', 'user', 'token', 'refresh_token',
+            'application', 'expires', 'scope',
+        )
+        read_only_fields = ('user', 'token', 'expires', 'application')       
path: awx/api/serializers.py
text: @user1:
try using only

```python
read_only_fields = ('*', 'application', 'user')
```

The star should inherit fields from the parent classes via the metaclass.

I don't know why 'token' or 'expires' would have ever been writable in the first place. And I'm not sure what we're doing with that user field.

@author:
That is much cleaner.  But wouldn't this make `description` immutable as well?

---------------------------------------------------
Nevermind /\ -- It also inherits whether the field was read_only or write_only (etc.) as well.